随着微服务架构的流行,RPC框架渐渐地成为服务框架的一个重要部分。在很多RPC的设计中,都采用了高性能的编解码技术,Protocol Buffers就属于其中的佼佼者。Protocol Buffers是Google开源的一个语言无关、平台无关的通信协议,其小巧、高效和友好的兼容性设计,使其被广泛使用。
概述
protobuf是什么?
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
- Google良心企业出厂的;
- 是一种序列化对象框架(或者说是编解码框架),其他功能相似的有Java自带的序列化、Facebook的Thrift和JBoss Marshalling等;
- 通过proto文件定义结构化数据,其他功能相似的比如XML、JSON等;
- 自带代码生成器,支持多种语言;
核心特点
- 语言无关、平台无关
- 简洁
- 高性能
- 良好的兼容性
为什么叫“Protocol Buffers”?
官方如是说:
The name originates from the early days of the format, before we had the protocol buffer compiler to generate classes for us. At the time, there was a class called ProtocolBuffer which actually acted as a buffer for an individual method. Users would add tag/value pairs to this buffer individually by calling methods like AddValue(tag, value). The raw bytes were stored in a buffer which could then be written out once the message had been constructed.
Since that time, the “buffers” part of the name has lost its meaning, but it is still the name we use. Today, people usually use the term “protocol message” to refer to a message in an abstract sense, “protocol buffer” to refer to a serialized copy of a message, and “protocol message object” to refer to an in-memory object representing the parsed message.
“变态的”性能表现
有位网友曾经做过各种通用序列化协议技术的对比,我这里直接拿来给大家感受一下:
序列化响应时间对比
序列化bytes对比
具体的数字
快速开始
以下示例源码已上传至github:ginobefun/learning_projects
新建一个maven项目并添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ginobefunny.learning</groupId> <artifactId>leanring-protobuf</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.2.0</version> </dependency> </dependencies> </project>
新建protobuf的消息定义文件addressbook.proto
syntax = "proto3"; // 声明为protobuf 3定义文件 package tutorial; option java_package = "com.ginobefunny.learning.protobuf.message"; // 声明生成消息类的java包路径 option java_outer_classname = "AddressBookProtos"; // 声明生成消息类的类名 message Person { string name = 1; int32 id = 2; string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; } message AddressBook { repeated Person people = 1; }
使用protoc工具生成消息对应的Java类
- 从已发布版本中下载protoc工具,比如protoc-3.2.0-win32;
- 解压后将bin目录添加到path路径;
- 执行以下protoc命令生成Java类:
protoc -I=. --java_out=src/main/java addressbook.proto
编写测试类写入和读取序列化文件
- AddPerson类通过用户每次添加一个联系人,并序列化保存到指定文件中。
public class AddPerson { // 通过用户输入构建一个Person对象 static AddressBookProtos.Person promptForAddress(BufferedReader stdin, PrintStream stdout) throws IOException { AddressBookProtos.Person.Builder person = AddressBookProtos.Person.newBuilder(); stdout.print("Enter person ID: "); person.setId(Integer.valueOf(stdin.readLine())); stdout.print("Enter name: "); person.setName(stdin.readLine()); stdout.print("Enter email address (blank for none): "); String email = stdin.readLine(); if (email.length() > 0) { person.setEmail(email); } while (true) { stdout.print("Enter a phone number (or leave blank to finish): "); String number = stdin.readLine(); if (number.length() == 0) { break; } AddressBookProtos.Person.PhoneNumber.Builder phoneNumber = AddressBookProtos.Person.PhoneNumber.newBuilder().setNumber(number); stdout.print("Is this a mobile, home, or work phone? "); String type = stdin.readLine(); if (type.equals("mobile")) { phoneNumber.setType(AddressBookProtos.Person.PhoneType.MOBILE); } else if (type.equals("home")) { phoneNumber.setType(AddressBookProtos.Person.PhoneType.HOME); } else if (type.equals("work")) { phoneNumber.setType(AddressBookProtos.Person.PhoneType.WORK); } else { stdout.println("Unknown phone type. Using default."); } person.addPhones(phoneNumber); } return person.build();