C++序列化工具最佳实践
序列化概述当两个服务在进行通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以字节序列的形式在络上发送。发送方需要把这个对象转换为字节序列,才能在络上发送;接收方需要把字节序列再恢复为对象。
当服务上线后,将领域对象以字节序列的方式存储在分布式数据库中。当该服务突然宕机后,其上的既有业务迁移到了其他同类服务实例上,这时需要从数据库中获取字节序列反构领域对象,使得业务不中断。
这个把对象转换为字节序列的过程被称为“序列化”(serialization),而它的逆过程则被称为“反序列化”(deserialization)。这两个过程结合起来,可以在异构系统中轻松地存储和传输数据。
两种用途:
把对象的字节序列保存在文件或数据库中;
在络上传送对象的字节序列。
必须序列化吗?
是的,核心问题是数据版本的前后项兼容,有了这个约束,就必须将对象序列化。
其他问题比如异构系统,虽然不是核心问题,但是序列化使得处理更加灵活。
C++序列化工具比较对于通信系统,大多都是C/C++开发的,而C/C++语言没有反射机制,所以对象序列化的实现比较复杂,一般需要借助序列化工具。开源的序列化工具比较多,具体选择哪一个是受诸多因素约束的:
效率高;
前后向兼容性好;
支持异构系统;
稳定且被广泛使用;
接口友好;
...
下面我们比较几个常见的C++序列化工具。
msgpack是一个基于二进制的高效的对象序列化类库,可用于跨语言通信,号称比protobuf还要快4倍,但没有类似于optional的关键字,所以msgpack至少不满足前后项兼容的约束。
cereal是一个开源的(BSDLicense)、轻量级的、支持C++11特性的、仅仅包含头文件实现的、跨平台的C++序列化库。它可以将任意的数据类型序列化成不同的表现形式,比如二进制、XML格式或JSON。cereal的设计目标是快速、轻量级、易扩展——它没有外部的依赖关系,而且可以很容易的和其他代码封装在一块或者单独使用,但不能跨语言,所以cereal至少不满足异构系统系统的约束。
protobuf是一种轻便高效的结构化数据存储格式,可用于结构化数据串行化,很适合做数据存储或RPC数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
在PC上单线程测试protobuf的性能结果如下:
单位数量平均字节数35序列化(1w次)时间(us)反序列化(1w次)时间(us)通过表格来综合比较一下这三种序列化工具:
protobuf满足通信系统对序列化工具的选型约束,同时具有简单和高效的优点,所以protobuf比其他的序列化工具更具有吸引力。
protobufC++使用指导protobuf安装在github上下载protobufC++版本,并根据的说明进行安装,此处不再赘述。
定义.proto文件proto文件即消息协议原型定义文件,在该文件中我们可以通过使用描述性语言,来良好的定义我们程序中需要用到数据格式。
我们先通过一个簿的例子来了解下:
//otosyntax=proto3;packageApp;messagePerson{stringname=1;int32id=2;stringemail=3;enumPhoneType{MOBILE=0;HOME=1;WORK=2;}messagePhoneNumber{requiredstringnumber=1;optionalPhoneTypetype=2[default=HOME];}repeatedPhoneNumberphone=4;}messageAddressBook{repeatedPersonperson=1;}正你看到的一样,消息格式定义很简单,对于每个字段而言可能有一个修饰符(repeated)、字段类型(bool/string/bytes/int32等)和字段标签(Tag)组成。
对于repeated的字段而言,该字段可以重复多个,即用于标记数组类型。
对于protobufv2版本,除过repeated,还有required和optional,由于设计的不合理,在v3版本把这两个修饰符去掉了。
字段标签标示了字段在二进制流中存放的位置,这个是必须的,而且序列化与反序列化的时候相同的字段的Tag值必须对应,否则反序列化会出现意想不到的问题。
生成.文件进入protobuf的bin目录,输入命令:
./protoc-I=../../test/protobuf--cpp_out=../../test/protobuf../../test/protobuf/otoI的值为.proto文件的目录,cpp_out的值为.h和.cc文件生成的目录,运行该命令后,在$cpp_out路径下生成了.h和文件。
protobufC++API生成的文件中有以下方法:
//nameinlineboolhas_name()const;inlinevoidclear_name();inlineconst::std::stringname()const;inlinevoidset_name(const::std::stringvalue);inlinevoidset_name(constchar*value);inline::std::string*mutable_name();//idinlineboolhas_id()const;inlinevoidclear_id();inlineint32_tid()const;inlinevoidset_id(int32_tvalue);//emailinlineboolhas_email()const;inlinevoidclear_email();inlineconst::std::stringemail()const;inlinevoidset_email(const::std::stringvalue);inlinevoidset_email(constchar*value);inline::std::string*mutable_email();//phoneinlineintphone_size()const;inlinevoidclear_phone();inlineconst::google::protobuf::RepeatedPtrField::tutorial::Person_PhoneNumberphone()const;inline::google::protobuf::RepeatedPtrField::tutorial::Person_PhoneNumber*mutable_phone();inlineconst::tutorial::Person_PhoneNumberphone(intindex)const;inline::tutorial::Person_PhoneNumber*mutable_phone(intindex);inline::tutorial::Person_PhoneNumber*add_phone();解析与序列化接口:
/*序列化消息,将存储字节的以string方式输出,注意字节是二进制,而非文本;string!=text,tethatthebytesarebinary,nottext;weonlyusethestringclassasaconvenientcontainer.*/boolSerializeToString(string*output)const;//解析给定的stringboolParseFromString(conststringdata);AnyMessageTypeprotobuf在V3版本引入AnyMessageType。
顾名思义,AnyMessageType可以匹配任意的Message,包含Any类型的Message可以嵌套其他的Messages而不用包含它们的.proto文件。使用AnyMessageType时,需要import文件google/protobuf/oto。
syntax=proto3;packageApp;importgoogle/protobuf/oto;messageErrorStatus{ydetails=1;}messageNetworkErrorDetails{int32a=1;int32b=2;}messageLocalErrorDetails{int64x=1;stringy=2;}序列化时,通过pack操作将一个任意的Message存储到Any。
//p::NetworkErrorDetailsdetails;t_a(1);t_b(2);App::ErrorStatusstatus;d_details()-PackFrom(details);std::stringstr;rializeToString(str);反序列化时,通过unpack操作从Any中读取一个任意的Message。
//p::ErrorStatusstatus;std::stringstr;rseFromString(str);for(constgoogle::protobuf::Anydetail:tails()){if(App::NetworkErrorDetails()){App::NetworkErrorDetailswork_error;packTo(work_error);INFO_LOG(NetworkErrorDetails:%d,%d,work_error.a(),work_error.b());}}protobuf的最佳实践对象序列化设计序列化的单位为聚合或独立的实体,我们统一称为领域对象;
每个聚合可以引用其他聚合,序列化时将引用的对象指针存储为key,反序列化时根据key查询领域对象,将指针恢复为引用的领域对象的地址;
每个与序列化相关的类都要定义序列化和反序列化方法,可以通过通用的宏在头文件中声明,这样每个类只需
治疗白癜风的最好医院北京可以治疗白癜风的医院