通信协议与序列化:网络通信基础(TCP/UDP)、RPC原理、主流序列化协议对比与选型
好,咱们进入第二章。这一章聊的是分布式系统的「血管」——通信。
你想想看,分布式系统说白了就是把一堆机器连起来干活。机器之间怎么说话?用什么语言?数据怎么打包?这些就是通信协议和序列化要解决的问题。我当年刚接触分布式时,觉得这不就是网络编程嘛,后来踩了不少坑才明白,这里面的门道深着呢。
网络通信基础:TCP vs UDP
先说说最底层的传输协议。TCP和UDP,这两个你肯定不陌生,但选型时很多人会纠结。
TCP,面向连接,可靠传输。它保证数据不丢、不乱序、不重复。代价是什么?三次握手、四次挥手、拥塞控制、重传机制……说白了就是「慢但稳」。
UDP,无连接,尽力而为。发出去就不管了,速度快,但可能丢包、乱序。适合实时性要求高的场景,比如视频通话、游戏。
我在项目中遇到过一个问题:一个实时监控系统,数据量不大但要求低延迟。团队一开始用了TCP,结果网络抖动时,TCP的重传机制导致延迟飙升,监控数据滞后了好几秒。后来换成UDP,配合应用层的简单确认机制,问题就解决了。
核心结论:
- RPC调用、数据库连接、消息队列 → 用TCP
- 实时音视频、游戏状态同步、日志采集 → 用UDP
- 大部分分布式中间件底层都是TCP,因为可靠性是基础
RPC原理:远程调用到底怎么玩?
RPC,Remote Procedure Call,远程过程调用。说白了就是「像调用本地方法一样调用远程服务」。
你想想看,A机器想调用B机器上的一个函数,中间要经过网络。怎么让这个过程对开发者透明?这就是RPC框架要解决的问题。
一个典型的RPC调用流程是这样的:
- 客户端调用本地代理(Stub)
- Stub把方法名、参数等信息序列化成二进制数据
- 网络传输:通过TCP发送到服务端
- 服务端反序列化,找到对应方法并执行
- 结果再序列化、传输回来
嗯,这里要注意:RPC的核心就是「序列化 + 网络传输 + 反序列化」。序列化选得好不好,直接决定了RPC的性能。
避坑指南:我曾经在一个项目里用了Java原生的序列化做RPC,结果线上频繁Full GC。为什么?Java序列化产生的数据太大,而且对象图复杂时性能极差。后来换成Protobuf,内存占用直接降了70%。
主流序列化协议对比
序列化协议,说白了就是把内存里的对象变成字节流,方便网络传输或持久化。反过来就是反序列化。
目前主流的序列化协议有:Protobuf、Thrift、JSON。咱们一个一个说。
Protobuf
Google出品,二进制协议。特点是:
- 体积小:比JSON小3-10倍
- 速度快:序列化/反序列化性能极高
- 强类型:需要定义.proto文件,生成代码
- 向后兼容:字段可以增删,不影响老版本
我个人的习惯是:内部服务间通信,首选Protobuf。尤其是高并发场景,它的性能优势非常明显。
举个例子,定义一个用户信息:
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated string tags = 4;
}
编译后生成Java/Python/C++代码,直接调用。简单、高效、可靠。
Thrift
Facebook开源,后来进了Apache。和Protobuf类似,也是二进制协议,但功能更丰富:
- 自带RPC框架:不只是序列化,还包含了传输层、协议层、处理器层
- 多语言支持:比Protobuf更广
- 多种传输协议:可以选二进制、压缩、JSON等
Thrift的定义文件长这样:
struct User {
1: i32 id,
2: string name,
3: string email,
4: list<string> tags
}
service UserService {
User getUser(1: i32 id)
}
你看,它把接口定义也包含进来了。如果你需要一个「开箱即用」的RPC方案,Thrift是个不错的选择。
JSON
这个不用多说了,文本协议,人类可读。优点是:
- 简单直观:调试方便,curl一下就能看结果
- 跨语言:几乎所有语言都支持
- 动态类型:不需要预定义schema
但缺点也很明显:体积大、解析慢、没有类型检查。适合对外API、调试场景、或者低频调用。
注意:JSON在分布式系统内部慎用。我见过一个团队用JSON做RPC,一次调用传输了2MB的JSON字符串,解析耗时50ms。换成Protobuf后,体积降到200KB,解析耗时不到2ms。性能差距就是这么明显。
选型建议
说了这么多,到底怎么选?我总结了一张表:
| 场景 | 推荐协议 | 理由 |
|---|---|---|
| 内部服务间RPC | Protobuf | 性能高、体积小、强类型 |
| 需要完整RPC框架 | Thrift | 自带传输层、多语言支持好 |
| 对外API/调试 | JSON | 人类可读、调试方便 |
| 实时流式数据 | Protobuf | 编码解码快、延迟低 |
| 异构系统集成 | JSON/Thrift | 语言兼容性好 |
我个人建议:能用二进制就别用文本。除非你明确需要人类可读,否则Protobuf是分布式系统的最佳选择。Thrift也不错,但如果你已经有服务发现、负载均衡等基础设施,Protobuf更轻量。
一个小技巧:如果你用Protobuf,记得开启「字段可选」特性(proto3的optional关键字)。这样在升级协议时,新增字段不会破坏老版本。我曾经因为没加这个,导致线上服务升级时大面积报错,教训深刻。
总结
这一章我们聊了:
- TCP和UDP的选型:可靠 vs 实时
- RPC的原理:代理、序列化、传输、反序列化
- 三种序列化协议的对比:Protobuf、Thrift、JSON
- 选型建议:内部用二进制,对外用JSON
下一章我们会深入RPC框架的实战,包括服务注册发现、负载均衡、熔断降级。到时候我会拿一个真实项目来拆解,敬请期待。
公众号:蓝海资料掘金营,微信deep3321