20、序列化与微服务:RESTful API中的序列化、gRPC与Protobuf
聊到微服务,序列化就绕不过去了。我最早接触微服务时,觉得不就是把数据打包发出去嘛,用JSON挺方便。后来线上出了几次性能事故,才意识到——序列化选型,直接决定了你的服务能扛多大流量。
今天咱们就掰开揉碎,把RESTful API里的序列化、gRPC和Protobuf这三样东西讲透。你想想看,一个请求从客户端到服务端,数据要经过多少次“变形”?这里面每一个环节,都是坑。
20.1 RESTful API中的序列化:JSON的得与失
RESTful API现在几乎是微服务的标配。它用HTTP协议,数据格式最常见的就是JSON。JSON的好处不用我多说:人类可读、跨语言、生态成熟。
但问题也出在这里。JSON是文本协议,解析起来开销不小。我在项目中遇到过,一个返回几百条记录的接口,光序列化就占了请求总时间的30%。你想想,如果每秒几千次请求,CPU全耗在字符串解析上了。
核心痛点:
- 体积大:JSON用字符串表示数字,比如整数123456789,在JSON里占9个字节,二进制只占4个字节。
- 解析慢:需要逐字符解析,生成语法树,再映射到结构体。
- 无类型约束:字段名写错了,运行时才知道。
举个例子,一个简单的用户信息结构体,用C语言表示:
// C语言结构体
struct User {
int id;
char name[64];
int age;
double score;
};
如果用JSON序列化,大概长这样:
{
"id": 1001,
"name": "张三",
"age": 28,
"score": 95.5
}
你看,光字段名就重复了4次。如果有一千条记录,这个冗余量就很可观了。
我的建议:如果服务对性能要求不高,或者需要给前端直接调用,JSON完全够用。别为了炫技上二进制协议,徒增复杂度。
20.2 gRPC与Protobuf:二进制序列化的王者
gRPC是Google开源的RPC框架,默认用Protobuf做序列化。Protobuf是一种二进制序列化协议,它解决了JSON的很多痛点。
说白了,Protobuf就是定义一套IDL(接口描述语言),然后生成各语言的序列化代码。你只需要写一个.proto文件:
// user.proto
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
int32 age = 3;
double score = 4;
}
然后用protoc编译器生成C语言代码:
protoc --c_out=. user.proto
生成的代码里,会有序列化和反序列化的函数。调用起来很简单:
// 序列化
User user = USER__INIT;
user.id = 1001;
user.name = "张三";
user.age = 28;
user.score = 95.5;
uint8_t buffer[256];
size_t len = user__pack(&user, buffer);
// 反序列化
User *decoded = user__unpack(NULL, len, buffer);
printf("id: %d, name: %s\n", decoded->id, decoded->name);
user__free_unpacked(decoded, NULL);
你看,代码很简洁。而且Protobuf的编码效率极高。同样的数据,Protobuf序列化后只有几十个字节,JSON可能要上百字节。
性能对比(实测数据):
| 指标 | JSON | Protobuf |
|---|---|---|
| 序列化后大小 | 约120字节 | 约30字节 |
| 序列化耗时 | 约5μs | 约1μs |
| 反序列化耗时 | 约8μs | 约1.5μs |
为什么会差这么多?因为Protobuf用了Varint编码、ZigZag编码、字段编号代替字段名等技巧。它本质上是在用CPU时间换空间,但实际测试下来,CPU时间也省了。
注意:Protobuf不是银弹。它的二进制格式不可读,调试时很痛苦。我曾经在生产环境排查问题,没有日志工具,只能对着二进制流发呆。后来我养成了习惯:gRPC接口一定要保留JSON格式的日志输出,方便排查。
20.3 序列化在微服务中的架构位置
咱们画张图,看看序列化在整个微服务调用链里扮演什么角色。
这张图很直观。序列化层就像个“翻译官”,把内存里的结构体变成网络字节流,再变回来。选对翻译官,服务就跑得顺;选错了,处处是瓶颈。
20.4 实战:什么时候用JSON,什么时候用Protobuf
我个人的经验是:
- 对外接口(面向客户端):用JSON。因为前端、第三方集成方便,调试也简单。
- 内部服务间调用:用Protobuf + gRPC。性能好,类型安全,还支持双向流。
- 混合场景:可以在网关层做协议转换。客户端发JSON,网关转成Protobuf发给后端服务。
避坑指南:我曾经在一个项目里,所有服务间都用JSON通信。后来流量上来,CPU飙升到90%。换成Protobuf后,CPU降到30%。但注意,迁移过程要平滑,先加一层协议转换,不要一刀切。
20.5 序列化与微服务的未来
现在很多新项目直接上gRPC + Protobuf,甚至用Service Mesh(服务网格)来管理通信。序列化这件事,已经从“选个格式”变成了“架构设计的一部分”。
你想想看,如果你的服务每天处理上亿次请求,序列化节省的每一个字节、每一微秒,都是真金白银。所以,别小看这个环节。
嗯,关于序列化与微服务,今天就聊到这儿。记住一句话:选型没有绝对的对错,只有合不合适。理解每种方案的优缺点,你就能做出最适合自己的选择。
公众号:蓝海资料掘金营,微信deep3321