• 1. Protocol Buffers序列化协议 及应用基础业务开发部 高磊 2010年10月15日
  • 2. 开篇语( TODO)
  • 3. 自我介绍基础业务开发部 高级技术经理 2006年开始参与飞信个人版服务器端的开发 负责个人版服务器端的系统架构规划 正在进行下一代服务器端平台FAE的设计与开发
  • 4. Protocol BuffersProtocol Buffers是Google开发的一种数据描述语言,能够将结构化数据序列化,可用于数据存储、通信协议等方面 官方网站 http://code.google.com/p/protobuf/ License - New BSD License 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广
  • 5. Agenda快速开始 语言及协议规范 开发示例 性能对比 RPC及应用
  • 6. 准备工作访问官方网站 http://code.google.com/p/protobuf/ 下载SDK http://code.google.com/p/protobuf/downloads/detail?name=protobuf-2.3.0.zip 如果不想自己编译,下载protoc工具 http://code.google.com/p/protobuf/downloads/detail?name=protoc-2.3.0-win32.zip
  • 7. 编写.proto描述文件 message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }
  • 8. 用protoc生成访问代码保存为person.proto文件 运行 protoc --cpp_out=src person.proto 得到如下文件 person.pb.cc person.pb.h 其他语言 Java: protoc –java_out=src person.proto Python: protoc –python_out=src person.proto C#: 使用第三方工具protobuf.net http://code.google.com/p/protobuf-net/
  • 9. 编写应用代码Person person; person.set_name("John Doe"); person.set_id(1234); person.set_email("jdoe@example.com"); fstream output("myfile", ios::out | ios::binary); person.SerializeToOstream(&output); fstream input("myfile", ios::in | ios::binary); Person person; person.ParseFromIstream(&input); cout << "Name: " << person.name() << endl; cout << "E-mail: " << person.email() << endl;
  • 10. 为什么不用XML在序列化结构化的数据时,相比与xml,protobuf有如下优点 简洁 消息大小只需要xml的1/10 ~ 1/3 解析速度快20 ~ 100倍 减少了二义性 可以生成更容易在编程中使用的数据访问代码
  • 11. 消息体对比XML(82 bytes) 1234 John Doe jdoe@example.com Protocol Buffers(31 bytes) 0A // Name字段标识 1 << 3 | 2 (字段类型:2,length-delimited) 08 4A 6F 68 6E 20 44 6F 65 // 08是字符串长度, 后面是John Doe 10 // Id字段标识 2 << 3 | 0 (字段类型:0,数字) D2 09 // 1234通过变长编码得到 1A // email字段标识 3 << 3 | 2 (字段类型:2,length-delimited) 10 6A 64 6F 65 40 65 78 61 6d 70 6C 65 2E 63 6F 6D // 10是字符串长度,后面jdoe@example.com
  • 12. protobuf解决的问题跨平台,跨语言 向下兼容性好,格式升级毫无压力 一次定义,多次生成,完全避免手写枯燥乏味且容易出错的解析代码 特别方便用于Rpc和消息存储的场合 Protocol buffers 现在是Google内部的标准数据定义语言, 在Google代码树种,现在有48,162种不同的消息定义在 12,183个 .proto文件中,用在RPC和数据存储的场合。 (引用自 http://code.google.com/apis/protocolbuffers/docs/overview.html)
  • 13. Agenda快速开始 语言及协议规范 开发示例 性能对比 RPC及应用
  • 14. 定义消息类型确定消息命名 定义字段类型 分配字段序号 序号范围1 ~ 229 - 1,除去19000 ~ 19999区间 序号是序列化的依据,名字只是参考message SearchRequest { required string query = 1; optional int32 page_number = 2; [default = 10]; optional int32 result_per_page = 3; [default = 10]; }
  • 15. 字段约束required: 必须赋值字段,禁止为空 optional: 可选字段,可以为空, [default = ] 默认值,如果一端报文中不包含可选字段,那么可选字段在反序列化的时候会赋为默认值,如果没有确定默认值,则可选字段将会使用类型的默认值,boolean为false,数字类型为0 repeated: 集合,可以填充零到多个对象, message SearchRequest { required string query = 1; optional int32 page_number = 2; [default = 10]; optional int32 result_per_page = 3; [default = 10]; }
  • 16. 定义更多的类型在同一个.proto文件中可以定义多个message 可以用C++风格的//起始的注释 message SearchRequest { required string query = 1; optional int32 page_number = 2; // 请求的页码 optional int32 result_per_page = 3; // 每页返回的值 } message SearchResponse { ... }
  • 17. 运行protoc生成代码当编写好.proto文件后,运行protoc编译器,会生成你选择语言的代码,针对不同的语言有不同的效果 C++: 编译器会为每个.proto文件生成.h和.cc文件,为每个message生成一个class Java: 编译器会为每个message生成一个.java文件 Python: 有一些小小的不同,python编译器会针对每个message生成一个静态的descriptor, C#: C#目前为非官方支持,可以用protobuf.net生成C#实体类,也可以直接用Attribute描述写成
  • 18. 标量类型protoTypeC++ TypeJava TypeC#Type (protobuf.net)Notesdoubledoubledoubledoublefloatfloatfloatfloatint32int32intint变长编码,对负数效率低, 请用sint32int64int64longlong变长编码,对负数效率低, 请用sint32uint32uint32intuint变长编码uint64uint64longulong变长编码sint32int32intint变长编码,对负数效率比int32高sint64int64longlong变长编码,对负数效率比int64高
  • 19. 标量类型(续)protoTypeC++ TypeJava TypeC# Type (protobuf.net)Notesfixed32uint32intuint固定4个字节,针对大于228的数据效率高fixed64uint64longulong固定8个字节,针对大于256的数据效率高sfixed32int32intint固定4个字节sfixed64int64longlong固定8个字节boolboolbooleanboolstringstringStringstring字符串需要UTF8或ASC-7编码bytesstringByteStringbyte[]
  • 20. Base-128 Varints 整数变长编码每个字节使用使用低7位表示数字,除了最后一个字节,其他字节的最高位都设置为1,例如: 数字1: 0000 0001 数字300: 1010 1100 0000 0010 000 0010 010 1100 → 000 0010 ++ 010 1100 → 100101100 → 256 + 32 + 8 + 4 = 300
  • 21. 消息格式一个Protocol Buffers的消息包含一系列字段,每个字段由一个变长32位整数作为字段头,后面跟随字段体 字段头格式 (field_number << 3) | wire_type field_number: 字段序号 wire_type: 字段编码类型
  • 22. 字段编码类型TypeMeaningUsed For0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum164-bitfixed64, sfixed64, double2Length-delimitedstring, bytes, embedded messages, packed repeated fields3Start groupgroups (deprecated)4End groupgroups (deprecated)532-bitfixed32, sfixed32, float
  • 23. 编码示例1: 整数编码 a = 150时,编码如下 08 96 01 08: 1 << 3 | 0 96 01: 1010 1100 0000 0010 → 010 1100 000 0010 → 150message Test1 { required int32 a = 1; }
  • 24. 编码示例2: 字符串编码 b = "testing"时,编码如下 12 07 74 65 73 74 69 6e 67 12: 2 << 3 | 2 07: 字符串长度 74 65 73 74 69 6e 67 → "testing" message Test2 { required string b = 2; }
  • 25. 编码示例3: 嵌套编码 设置c.a = 150时,编码如下 1a 03 08 96 01 1a: 3 << 3 | 2 03: 嵌套结构长度 08 96 01 →Test1 { a = 1} message Test3 { required Test1 c = 3; }
  • 26. 其他编码规范optional的字段不会出现在消息报文中 repeated的字段会出现多次(没设置[packed = true])的情况下 [packed = true]选项可以针对整数字段进行优化 message Test4 { repeated int32 d = 4; } d = {3, 270, 86942}; 22 // tag (field number 4, wire type 2) 06 // payload size (6 bytes) 03 // first element (varint 3) 8E 02 // second element (varint 270) 9E A7 05 // third element (varint 86942)
  • 27. 定义枚举类型枚举类型使用变长编码,必须为32位整数 message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3 [default = 10]; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } optional Corpus corpus = 4 [default = UNIVERSAL]; }
  • 28. 引用其他的消息类型可以在消息定义中嵌套其他的消息类型 message SearchResponse { repeated Result result = 1; } message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; }
  • 29. 使用import引入其他文件中的定义如果.proto文件定义在许多不同的文件中,可以用import关键字导入其它的定义后使用其它.proto文件中定义的message type 使用protoc编译的时候,需要添加设置 -I/--import_path 参数去确定import的目录,如果不指定import目录,默认为调用protoc的当前目录 import "myproject/other_protos.proto";
  • 30. 嵌套定义可以在message定义内部定义message type 引用时需要全名,如SearchResponse.Result 可以多层嵌套message SearchResponse { message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } repeated Result result = 1; } message SomeOtherMessage { optional SearchResponse.Result result = 1; }
  • 31. 升级规范如何保持向下兼容性 不改变任何已经存在的字段的序号 新增字段应该标记为optional或repeated 不再需要的字段可以删除,但建议先加上OBSOLETE_前缀,这样可以保证序号被保留 默认值都是反序列化时产生的,但要保证序列化方没有将默认值当成一个有意义的值进行传输 字段类型兼容 int32, uint32, int64, uint64, bool互相兼容 sint32 sint64 互相兼容,但不兼容其他整形数字 在utf-8编码下,string和bytes是兼容的, Fixed32与sfixed32兼容, fixed64与sfixed64兼容
  • 32. Agenda快速开始 语言及协议规范 开发示例 性能对比 RPC及应用
  • 33. 演示: C++ Demo
  • 34. 演示: Java Demo
  • 35. 演示: C# Demo
  • 36. Agenda快速开始 语言及协议规范 开发示例 性能对比 RPC及应用
  • 37. 性能比较使用一个统一的消息体格式 评估以下性能指标 序列化速度 报文大小 Java Java Serialize XML: JAXB JSON: 使用gson库 protobuf: 标准 C# .Net Serialize: Protobuf protobuf.net C++ Protobuf:
  • 38. 性能评估结果报文大小 (Bytes)序列化耗时 (ms)反序列化耗时 (ms)Java Serialize16109971037Java XML414124684001Java Json31531143717351Java Protobuf933776422C# .Net Serialize272645733185C# Protobuf933488673C++ Protobuf933282297
  • 39. Agenda快速开始 语言及协议规范 开发示例 性能对比 RPC及应用
  • 40. 远程调用的种类开放协议 Web Service 平台私有协议 .Net Remoting .Net WCF Java RMI 其他常用的协议 xml over Http
  • 41. Protocol Buffers对Rpc的支持支持在.proto文件中定义Rpc Service 能够通过protoc生成框架代码 通过实现RpcChannel抽象类,完成Rpc在通信层的交互 第三方实现 http://code.google.com/p/protobuf/wiki/ThirdPartyAddOns
  • 42. 用.proto方式定义远程调用 service SearchService: 服务名称 rpc Search: 方法名 SearchRequest 请求类型 SearchResponse 应答类型 service SearchService { rpc Search (SearchRequest) returns (SearchResponse); }
  • 43. 选择通信层协议对通信层协议的要求 有Transaction的处理机制 可以传送二进制报文 长连接或短连接均可 适合的协议 HTTP Sipc Tcp(需要自己添加Transaction管理,连接控制,并发控制等功能)
  • 44. 制定一个简单的Rpc协议(over Http) RequestPOST rpc.do?service=&method= HTTP/1.1 User-Agent: user-agent-value Host: host-value Content-Type: binary Content-Length: length ResponseHTTP/1.1 200 Content-Length: length
  • 45. Rpc Framework 的实现包装客户端框架代码 异步/同步接口 服务方法的封装 协议层的管理 异常处理 包装服务器端实现代码 实现服务代码的框架 服务器端协议栈 服务的分发及应用线程的管理 异常处理,日志处理
  • 46. Rpc Framework:其他的考虑日志 调用监控 通信层协议的优化 应用线程池的保护 路由策略
  • 47. 个人版服务器端远程调用的发展V2 Sipc Remoting V3 Xml over Sipc Remoting V4 protobuf over Sipc protobuf over Http protobuf over Namedpipe protobuf over Tcp (完善中)
  • 48. 谢谢Questions?