这是 gRPC 系列博客的第一篇,本篇将简要介绍 Protobuf 这一gRPC定义语言。

Protocol Buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.
(笔者译:Protocol Buffers 是一个语言中性的、平台中性的拓展性序列化结构数据的机制)

Protocol buffers are a combination of the definition language (created in .proto files), the code that the proto compiler generates to interface with data, language-specific runtime libraries, and the serialization format for data that is written to a file (or sent across a network connection).
(笔者译:Protocal Buffers 是下列功能的集合:定义语言(通过.proto文件)、生成接口和数据代码的 proto 编译器,特定语言的运行库、以及存储到一个文件(或通过网络传输)的序列化格式

虽然接触这个“语言”的缘起是 gRPC, 而实际上它比 gRPC 更为底层。
gRPC的服务通过.proto定义,gRPC 的数据也是通过 protobuf 传输。
而实际上,protobuf的主要使用也是gRPC。(虽然也可以不使用gRPC,后文会提到)

可以这么说:

  • gRPC 相当于 RESTful API
  • protobuf 相当于 JSON/XML

读者需要知道的是 protobuf 要比 JSON 更快,且更方便。

顺便需要提一下 Apifox 现在已经支持对 gRPC API 进行调试,笔者的实际使用感受还不错。

Protobuf 语法简述

接下来将介绍 Protobuf 的基本语法,如果嫌麻烦可以直接跳到本节后面的语法要点总结和语法风格指南。

protobuf 有两个版本: 2和3,笔者没用过2, 所以只介绍3。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
syntax = "proto3"; // 定义版本

package project.user; // 申明package

import "project/foo.proto"; // 导入一个proto

enum Role { // 枚举 角色
	BOSS = 0; // 默认值
	EMPLOYEE = 1;
}

message User {
	uint64 id = 1;
	string name = 2;
	string password = 3;
	Role role = 4; // 可以嵌套
	string email = 5;
}

message GetAllUserResponse {
	repeated User = 1;  // repeated 表示这个字段可以重复(是个列表)
						// 可以嵌套使用其他的 message
}

message RegisterUserRequest {
	optional string name = 1; // optional 表示该字段可选
	string password = 2; 
}

注意到,每个消息字段需要分配一个字段编号(Field Numbers),编号有如下规则:

  1. 范围为 1 ~ 536,870,911, 且 19,000 ~ 19,999 不能使用(Protobuf 保留)
  2. 每块内的编号必须是唯一的
  3. 不能使用被回收(reserved, 见后文)的编号。

一般说来,一次性定义一个完美的 protobuf 是几乎不可能的,如果需要给某些message添加新的字段是简单的,只要使其编号符合规矩。
更新一个字段的类型也是不合适的,应当删除这个字段,并申明一个新的字段。
而删除一个字段有可能造成严重的问题,一个正确的删除字段的方式是在删除的同时,申明对该字段的回收。

1
2
3
4
5
6
7
8
message User {
	uint64 id = 1;
	string name = 2;
	string password = 3;
	Role role = 4; // 可以嵌套
	// string email = 5;
	reserved 5;
}

定义服务

1
2
3
4
service UserService {
	rpc Login(LoginRequest) returns LoginResponse; 
	// LoginRequest , LoginResponse 都是 message
}

语法要点

  1. 第一行必须定义版本
  2. 花括号为代码块标志,C风格的注释,行末分号
  3. 每个字段需要分配一个唯一的字段编号
  4. 不要更新字段的类型,删除字段后要回收编号

参考文献