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 序列化在微服务中的架构位置

咱们画张图,看看序列化在整个微服务调用链里扮演什么角色。

客户端 (App/浏览器) 序列化/反序列化层 JSON / Protobuf 服务端 HTTP请求 反序列化 序列化 HTTP响应 序列化在微服务调用中的位置 1. 客户端发起HTTP请求,数据以JSON/Protobuf格式传输 2. 服务端收到请求后,先反序列化,还原为内存结构体 3. 服务端处理完业务逻辑,将结果序列化,返回给客户端 4. 客户端反序列化,拿到最终数据

这张图很直观。序列化层就像个“翻译官”,把内存里的结构体变成网络字节流,再变回来。选对翻译官,服务就跑得顺;选错了,处处是瓶颈。

20.4 实战:什么时候用JSON,什么时候用Protobuf

我个人的经验是:

  • 对外接口(面向客户端):用JSON。因为前端、第三方集成方便,调试也简单。
  • 内部服务间调用:用Protobuf + gRPC。性能好,类型安全,还支持双向流。
  • 混合场景:可以在网关层做协议转换。客户端发JSON,网关转成Protobuf发给后端服务。

避坑指南:我曾经在一个项目里,所有服务间都用JSON通信。后来流量上来,CPU飙升到90%。换成Protobuf后,CPU降到30%。但注意,迁移过程要平滑,先加一层协议转换,不要一刀切。

20.5 序列化与微服务的未来

现在很多新项目直接上gRPC + Protobuf,甚至用Service Mesh(服务网格)来管理通信。序列化这件事,已经从“选个格式”变成了“架构设计的一部分”。

你想想看,如果你的服务每天处理上亿次请求,序列化节省的每一个字节、每一微秒,都是真金白银。所以,别小看这个环节。

嗯,关于序列化与微服务,今天就聊到这儿。记住一句话:选型没有绝对的对错,只有合不合适。理解每种方案的优缺点,你就能做出最适合自己的选择。


公众号:蓝海资料掘金营,微信deep3321