在分布式系统、前后端交互、RPC 远程调用中,序列化是绕不开的核心技术。早期以 XML + SOAP 为主,如今 JSON 几乎一统天下,而高性能场景下 Protobuf 又不可或缺。本文用大白话 + 代码示例 + 对比表格,带你彻底理解 XML、SOAP、JSON 的本质、区别以及为什么 JSON 能成为主流。
一、XML 是什么?和序列化有什么关系?
1. 核心定义
XML 即可扩展标记语言,本质是用标签描述数据的文本格式。
<user>
<name>张三</name>
<age>25</age>
<email>zhangsan@example.com</email>
</user>
它最初用于文档标记,后来被广泛用作序列化协议,把 Java 对象、C++ 结构体转成通用文本,实现跨机器、跨语言传输。
2. XML 关键特点
- 跨机器、跨语言:所有平台都能解析
- 自我描述性:标签名直接说明数据含义
- 冗长复杂:标签多,体积大,解析慢
- 自带 IDL 能力:通过 DTD/XSD 约束结构
3. 常见用途
- Spring、MyBatis 等框架配置文件
- 早期 WebService 数据传输
- Office 文档底层格式(docx 等)
二、SOAP 是什么?和 RPC/WebService 的关系
1. 核心定义
SOAP = Simple Object Access Protocol
基于 XML 的 RPC 协议,也就是用 XML 做序列化的远程调用方案。
基于 SOAP 的服务称为 WebService,是早年分布式系统的标准架构。
2. SOAP 与 RPC 组件对应关系
| RPC 组件 | SOAP 对应 | 简单说明 |
|---|---|---|
| IDL | WSDL | 描述接口、参数、返回值 |
| 序列化 | XML | 把方法调用封装成 XML 消息 |
| Stub/Skeleton | 框架自动生成 | 客户端代理与服务端骨架 |
| 传输层 | HTTP(最常用) | 基于 HTTP 传输 XML 数据 |
3. SOAP 优缺点
优点:
- 跨语言、跨平台
- 安全规范完善(WS-Security)
- 支持多种传输协议
缺点:
- XML 冗长,消息体积大
- 解析慢,性能差
- 配置复杂,开发效率低
4. XStream 简单说明
Java 中常用的 XML 序列化工具,可以直接把 POJO 转成 XML,无需 IDL 和编译,适合单语言内部系统。
三、SOAP 的递归/自我描述结构
SOAP 的结构非常有意思,形成了"自己描述自己"的递归关系:
SOAP 使用 WSDL 作为接口描述
WSDL 使用 XSD 定义结构
XSD 本身就是 XML 文件
类比:用中文写一本《中文语法书》,用语言本身来描述语言规则,这就是自我描述 + 递归结构。
四、JSON 到底是什么?
1. 核心定义
JSON = JavaScript Object Notation
本质是键值对(key-value)格式的文本数据。
{
"userId": "1",
"name": "measi",
"address": [
{
"city": "北京",
"postcode": "1000000",
"street": "wangjingdonglu"
}
]
}
JSON 现已成为全球最通用的序列化格式。
2. JSON 六大优点
- 符合开发者对对象的直观理解
- 人眼可读性高
- 比 XML 简洁,体积更小
- JS 原生支持,是 Ajax 事实标准
- 协议简单,解析速度快
- 结构松散,扩展性、兼容性极强
3. 什么是 JSON 的 IDL 悖论?
RPC 框架(gRPC/SOAP)都需要 IDL 来约定结构,但 JSON 看起来完全不需要 IDL 也能跨语言。
真相:
- 动态类型语言(JS/PHP)天然匹配键值对
- 静态类型语言(Java)通过反射实现自动映射(Gson/Jackson)
一句话总结:JSON 看似没有 IDL,实际上是用动态适配 + 反射替代了 IDL 与编译流程。
五、松散的关联数组 = 极强的扩展性与兼容性
1. 关键概念
- 关联数组 = key-value 结构
- 松散 = 没有强制结构约束,可随意增删字段
- 可扩展:随时加字段
- 兼容:旧代码不会因为新增字段报错
2. 举个例子
接口 V1:
{ "name":"张三", "age":25 }
接口 V2 新增 phone:
{ "name":"张三", "age":25, "phone":"138xxxx1234" }
JSON 解析器会自动忽略不认识的字段,旧客户端完全不报错,无需同步升级。
3. 对比「严格结构」的 XML
XML 是严格结构,必须用 DTD/XSD 提前定义好所有字段,相当于「写死了合同」:
<!-- 必须提前用XSD定义结构,少一个、多一个字段都会报错 -->
<user>
<name>张三</name>
<age>25</age>
<!-- 新增phone字段,必须先改XSD,否则解析直接报错 -->
<phone>138xxxx1234</phone>
</user>
XML 的问题:
- 新增字段必须先改 XSD(IDL),再重新编译、部署,旧客户端不更新就会直接报错
- 前后端必须同步发版,否则接口直接崩,迭代成本极高
- 兼容性极差,稍微改个结构就出问题
4. 对比「强类型 IDL」的 Protobuf
Protobuf 是严格 IDL + 二进制格式,必须在 .proto 文件里提前定义所有字段:
message User {
string name = 1;
int32 age = 2;
// 新增phone必须先改.proto,重新生成代码
string phone = 3;
}
- 虽然 Protobuf 也做了向后兼容(新增字段不影响旧代码),但必须提前在 IDL 里声明,不能像 JSON 一样「随便加」
- JSON 的「松散」是无约束的灵活,Protobuf 是「有约束的兼容」,两者灵活度完全不是一个量级
六、JSON 适用与不适用场景
适用场景:
- 前后端 Ajax 交互
- 轻量级微服务、对外接口
- 接口频繁迭代、需要兼容旧版本
- 需要穿透防火墙的 HTTP 接口
不适用场景:
- 超大数据量存储(体积大)
- 金融、核心交易等强契约场景
- 高性能、低延迟场景(游戏、实时通信)
七、JSON vs XML/SOAP vs Protobuf 全面对比
| 特性 | JSON | XML/SOAP | gRPC + Protobuf |
|---|---|---|---|
| 格式 | 文本键值对 | 文本标签 | 二进制 |
| IDL | 不需要(反射) | 需要 WSDL | 需要 .proto |
| 性能 | 中 | 低 | 极高 |
| 可读性 | 高 | 高 | 低 |
| 扩展性 | 极强(无约束灵活) | 差(严格约束) | 良好(有约束兼容) |
| 开发效率 | 极高 | 低 | 高 |
| 主流场景 | 前后端、通用接口 | 老旧企业系统 | 微服务、高性能内网 |
八、Protobuf/gRPC 接口频繁变更的兼容与演进方案
如果你已经选择了 Protobuf/gRPC,但面临接口频繁变更、多服务协同的挑战,可以通过以下方式系统性地管理扩展和演进,避免版本不一致导致无法调用的问题。
一、从 Protobuf 本身:遵循兼容性规则
Protobuf 在设计上支持一定程度的向前/向后兼容,前提是严格遵守其字段规则:
| 操作 | 是否兼容 | 正确做法 |
|---|---|---|
| 新增字段 | ✅ 兼容 | 使用新的 tag 编号,并设为 optional 或带默认值。旧客户端会忽略该字段。 |
| 删除字段 | ⚠️ 有限兼容 | 不要直接删除 tag,而是标记为 reserved,防止未来重用 tag 导致混乱。 |
| 修改字段类型 | ❌ 不兼容 | 除非类型是兼容的,一般禁止修改。 |
| 修改字段名 | ✅ 兼容 | 因为 wire 格式只认 tag 编号,改名不影响兼容性。 |
| 修改包名/服务名 | ❌ 不兼容 | 会改变 gRPC 的路径,需要视为破坏性变更。 |
| 新增服务或方法 | ✅ 兼容 | 不影响已有调用。 |
实践要点:
- 每个 .proto 文件应该用
syntax = "proto3";并开启 optional 支持 - 使用
reserved字段保护已删除的 tag 和字段名 - 所有变更都应该通过 code review 检查是否破坏了兼容性
二、版本管理:统一 proto 仓库,控制生成代码
你之前遇到的"pb.go 版本不一致"本质上是 proto 文件及其生成代码没有统一管理。
- 采用单一 proto 仓库(或 monorepo 统一管理)
- 将所有的 .proto 文件集中在一个独立的 Git 仓库中,统一版本
- 服务通过依赖管理工具引入生成代码,而非各自生成
- 自动化生成代码与版本发布
- CI 自动生成多语言代码,发布到包仓库
- 显式升级 proto 包版本,保证全服务统一
- 使用工具强制 lint 和 breaking change 检测
- 使用 Buf 检查规范、拦截破坏性变更
- 结合 CI 从源头避免兼容问题
三、服务演进策略:减少耦合影响
- API 版本化:服务名加入版本号(UserServiceV1/V2),新旧版本并存
- 适配层隔离:通过 BFF/gRPC-Gateway 隔离内部 proto 与外部接口
- 契约测试:CI 中验证服务调用兼容性
四、混合策略:核心稳定领域用 gRPC,高频变动用 JSON
- 核心低延迟服务:保留 gRPC,统一版本管理
- 高频变更入口:改用 JSON/HTTP,通过 BFF 转换
- 单服务可同时暴露 gRPC + HTTP 端点,按需选择
五、总结:Protobuf 扩展的"正确姿势"
| 问题 | 解决方案 |
|---|---|
| proto 版本不一致 | 统一 proto 仓库 + 自动生成代码 + 版本化发布 |
| 破坏性变更检测 | 使用 Buf 在 CI 中拦截 breaking change |
| 字段频繁增加 | 严格遵守兼容性规则(新 tag + optional) |
| 接口需要独立演进 | 服务版本化(v1/v2) |
| 对外暴露不稳定的 API | 通过 gRPC-Gateway 或 BFF 暴露 JSON,内部保持 gRPC |
| 团队间强耦合 | 将 proto 视为"服务间契约",变更需沟通并走依赖升级流程 |
九、全文总结
- XML:早期通用序列化格式,可读但臃肿,多用于配置文件,结构严格、兼容性差。
- SOAP:基于 XML 的 RPC 协议,安全规范但性能差,现已逐渐淘汰。
- JSON:键值对结构,松散灵活,兼容性极强,是当前互联网接口事实标准。
- JSON 的核心优势:结构松散,扩展无需改旧代码,前后端可异步迭代,开发效率极高。
- 高性能内网调用优先选择 Protobuf + gRPC ,对外接口与前后端交互首选 JSON。
- gRPC 接口变更需遵循兼容规则+统一版本管理,兼顾性能与迭代效率。