通信协议与序列化:网络通信基础(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调用流程是这样的:

  1. 客户端调用本地代理(Stub)
  2. Stub把方法名、参数等信息序列化成二进制数据
  3. 网络传输:通过TCP发送到服务端
  4. 服务端反序列化,找到对应方法并执行
  5. 结果再序列化、传输回来

嗯,这里要注意: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