附近的人序列化之白送篇---msgpack大战protobuf

Posted 高性能API社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了附近的人序列化之白送篇---msgpack大战protobuf相关的知识,希望对你有一定的参考价值。

大家好,老李是我,今天这篇算是白送的,尽管白送但还是要贴防喷杠条款,这年月真是...

  • 本文没有什么亮点不高端不涉及高性能高并发而且网上一搜一大把发誓文章没有蹭mp和pb热点最后的末尾有会挂一个微信的广告

  • 本文可能会存在错误欢迎公号留言指出或者公正讨论


常见的序列化/反序列化四大小王子:

  • thrift

  • msgpack

  • protobuf

  • json


第一个和第四个就不说了昂,然后是各种语言内置的序列化与反序列化函数也不说了昂。一般说来说各种各样的语言都有着自己各种花式数据结构和对象,但各种语言之间的这些数据结构和对象都是彼此不开眼的。比如Golang的struct和php的object,不开眼;Java的map和Python的dict,不开眼。如果说这几种语言之间想小刀剌屁股---开开眼,那么最初的方案可能是xml,然后再后来成了json...


所以序列化和反序列化要解决的问题就是:

  • 将不同语言的特有数据结构搞成大家都认的二进制串子

  • 将大家都认的二进制串子搞成不同语言的特有数据结构


说到这里应该get到点了,如果在一大坨微服务构成的庞大业务系统中,各个业务系统之间飞数据的序列化反序列化部分需要技术选型时候,参考标准是什么?


  • 性能?序列化和反序列化的速度越快越好,序列化后的数据占用空间越小越好,你觉得有没有道理?

  • 可读性?可读性最好的应该是json咯?这里主要说的是序列化完毕后你肉眼看到的是一坨什么玩意,如果一眼就能看到序列化后那坨玩意是啥样想必会好很多,你觉得合情不?

  • 扩展兼容?如果业务频繁改动,添加新字段还要兼容原来业务,对扩展性和兼容性就应该有讲究了,你觉得合理不?

  • 健壮性和通用性?主要是通用性吧,要知道如果这个序列化反序列化方案属于某个平台语言专有,那还是很难受的。一个成熟完整的方案一定能够兼顾众多语言众多特性,比如说thrift吧假如TA不支持Java的map或者对php的array支持不好,就有点儿扯了,你觉得在理不?


然而其实大家还是只看中第一条


我要开始表演了,你们准备好复制粘贴了吗?




msgpack


这玩意的官网是https://msgpack.org/,打开你应该能发现一大坨各种各样包括你没见过的语言对msgpack的实现,比如Golang比如PHP比如C,下面看下PHP和Golang的case吧。


先说PHP的,PHP对msgpack的实现最好用鸟哥实现的那个msgpack扩展,使用起来非常非常非常粗暴简单,并同时与JSON简单对比一下,你们感受下:


<?php$user = array('id' => mt_rand( 10000000, 9999999999 ),'username' => 'etc'.time(),'password' => md5( time() ),'avatar' => 'http://cdn.qiniu.com/user/big-avatar-png.png','age' => 12,'gender' => 'male','remark' => array(array('id' => 1,'title' => '从呢结果 i 难过我的卡带','remark' => '空间看撒附近卡积分阿空间和饭卡金额为阿看到肌肤垃圾发酵了房间', ),array('id' => 1,'title' => '从呢结果 i 难过我的卡带','remark' => '空间看撒附近卡积分阿空间和饭卡金额为阿看到肌肤垃圾发酵了房间', ),array('id' => 1,'title' => '从呢结果 i 难过我的卡带','remark' => '空间看撒附近卡积分阿空间和饭卡金额为阿看到肌肤垃圾发酵了房间', ), ),);$counter = 900000;$begin = microtime( true );for( $i = 1; $i <= $counter; $i++ ) { $msg = msgpack_pack( $user ); $data = msgpack_unpack( $msg );}$end = microtime( true );echo "msg序列化和反序列化{$counter}次:".( $end - $begin ).PHP_EOL;
$begin = microtime( true );for( $i = 1; $i <= $counter; $i++ ) { $msg = json_encode( $user ); $data = json_decode( $msg, true );}$end = microtime( true );echo "json序列化和反序列化{$counter}次:".( $end - $begin ).PHP_EOL;


msgpack的PHP API一共就两个函数:msg_pack和msg_unpack,用起来非常粗暴,执行下刚才的php文件看下结果:



下面看下Golang的msgpack的demo,感受下:


package mainimport ("fmt""time""github.com/shamaton/msgpack")type AvatarItem struct { Id int Url string}type User struct { Id int Username string Password string Age int Gender string Avatar []AvatarItem}func main() { avatar := AvatarItem{ Id:12, Url:"http://cdn.qiniu.com/avatar.png", } user := User{ Id:123, Username:"xiaodushe", Password:"3fjkwejk3jb3h", Age:44, Gender:"male", Avatar:[]AvatarItem{ avatar, avatar, avatar, }, } begin := time.Now().UnixNano()for i := 1; i <= 900000; i++ { encodeUser, err := msgpack.Encode( user )if err != nil {panic( err ) } user = User{} err = msgpack.Decode( encodeUser, &user ) } end := time.Now().UnixNano() ret := end - begin fmt.Println( "msgpack序列化反序列化900000次:", ret )}


注意这里用的时间单位是UnixNano,你们感受下:


【附近的人】序列化之白送篇---msgpack大战protobuf




protobuf


相对msgpack而言,protobuf可能稍微麻烦些,protobuf有一个类似于thrift idl的流程,就是需要先定义好数据结构,对于广大php而言,这么做似乎又有一个好处:莫名其妙数据结构变化或者数据类型发生变化。


首先你得安装好proto-compiler(类比thrift-compiler),这个八仙过海哈,八仙过海~这还还不够,你还需要golang proto插件(注意与proto-compiler是两回事),命令如下:


go get github.com/golang/protobuf/protoc-gen-go


其次是再安装好protobof FOR golang的库文件,注意这和上面protoc-compiler和protoc-gen-go插件又是两码子事儿:


go get github.com/golang/protobuf/proto


然手定义好数据结构,也就是一个proto文件,比如下面这个(先说了昂,这里就不普及proto文件的语法了):


syntax = "proto2";package protopack;enum FOO{X = 0;};message User{required string message = 1;required int32 length = 2;required int32 cnt = 3;}


最后,使用protoc-compiler生成一下相关文件:


protoc --go_out=./protopack *.proto


然后在你golang的工作目录下的protopack文件夹(自己手工建一个protopack)中生成一个go文件,这个文件就是存放数据结构的地方,截个图你们感受下:



测试用的golang demo代码如下:


package mainimport ("fmt""protopack""github.com/golang/protobuf/proto")func main() { message := "xiaodushe"var length int32 = 12var cnt int32 = 34 ret := &protopack.User{ Message: &message, Length : &length, Cnt : &cnt, }  // protobuf encode 序列化 encodeData, err := proto.Marshal( ret )if err != nil {panic( err ) } fmt.Println( encodeData )// protobuf decode 反序列化 decodeData := &protopack.User{} err = proto.Unmarshal( encodeData, decodeData )if err != nil {panic( err ) } fmt.Println( decodeData )}


go run下?



估计会有老铁好奇,为什么不顺带做个语言之间的评测比赛啥的,这个咳,根据我的一贯经验,能引起争论的东西最好不要发也不要说,大部分人是理性的可以好好说话,但总有你惹不起的人,所以想做个test的诸位自己跑一下?


一个周末,两篇原创,一百多行山寨Redis C代码,没白过...