
第四章数据编码与演化这一章讨论一个看似底层、实则贯穿所有分布式系统的问题数据在不同进程、不同机器、不同版本之间传输时用什么格式编码当数据结构变化时新旧代码如何兼容Kleppmann 的核心论点是编码格式决定了系统能否平滑演化而兼容性是分布式系统长期可维护的关键。本章逻辑先讲编码格式内存 vs 磁盘/网络再讲三种数据流模式数据库、服务、消息队列最后落到兼容性与演化的实践。一、本章的核心问题任何数据系统都面临两个问题编码Encoding内存中的对象如何变成字节以便存储或传输演化Evolution当数据结构变化时旧代码和新代码如何共存这两个问题在单机程序里可能不明显但在分布式系统中是生死攸关的滚动升级时新旧版本代码同时运行数据库中的旧数据可能被新代码读取微服务之间版本不同步Kleppmann 的核心观点数据格式的兼容性决定了系统能否独立演化。向后兼容让新代码读旧数据向前兼容让旧代码读新数据。两者都需要但前者容易后者困难。二、编码格式的两大阵营1. 语言内置序列化Java 的Serializable、Python 的pickle、Ruby 的Marshal问题与语言绑定跨语言困难安全漏洞反序列化可执行任意代码版本兼容性差性能一般结论不推荐用于持久化或网络传输2. 文本格式1JSON优点人类可读、跨语言、广泛支持缺点数字类型模糊没有区分整数和浮点不支持二进制数据需 Base64模式可选缺乏约束解析较慢2XML优点成熟、有 Schema 支持缺点冗长、复杂、解析慢使用场景配置文件、SOAP3CSV优点简单、表格数据友好缺点无模式、类型模糊、转义规则混乱文本格式的共同问题体积大数字用字符串表示解析开销大类型信息丢失3. 二进制格式1ThriftFacebook两种编码BinaryProtocol紧凑字段用 ID 标识CompactProtocol更紧凑用可变长度整数支持模式演化字段有 ID可以增删字段新增字段必须可选删除字段只能删可选字段2Protocol BuffersGoogle类似 Thrift但更简洁字段用tag编号标识支持向前/向后兼容新增字段旧代码忽略新代码用默认值删除字段不能复用 tag 编号广泛用于 gRPC、微服务3AvroApache起源于 Hadoop 生态核心特点写入时模式Writer’s Schema与读取时模式Reader’s Schema分离数据本身不带模式或带一个模式 ID读取时用读者模式解释写者模式的数据两者通过**模式解析Schema Resolution**对齐优势数据紧凑不需要字段 ID模式演化灵活适合动态生成模式如数据库导出二进制格式对比维度ThriftProtobufAvro字段标识字段 IDTag 编号无依赖模式模式携带数据中数据中写入时模式演化灵活性中中高紧凑度高高最高动态模式弱弱强代表场景Facebook 内部gRPC、微服务Hadoop、Kafka三、数据流模式Dataflow数据编码不是孤立的它总是存在于某种数据流中。Kleppmann 讨论了三种1. 数据库中的数据流数据写入数据库以后被读取编码格式数据库自己决定如 InnoDB 的行格式演化问题旧数据被新代码读取向后兼容新数据被旧代码读取向前兼容关键实践用 schema 演化支持字段增删避免破坏性变更考虑数据迁移策略2. 服务中的数据流REST/RPC1REST基于 HTTP资源导向优点简单、跨语言、可缓存缺点无正式模式依赖文档演化通过 URL 版本化、内容协商2RPC远程过程调用让远程调用看起来像本地调用问题网络不可靠超时、重试延迟不可忽略参数序列化版本耦合代表gRPC用 Protobuf、Thrift RPC、Java RMIKleppmann 的批评RPC 试图隐藏网络但网络问题无法隐藏反而让问题更难处理3服务演化微服务架构下服务独立部署、独立演化需要API 版本化向后/向前兼容消费者驱动的契约测试3. 消息队列中的数据流1消息代理Message Broker异步通信解耦生产者和消费者代表RabbitMQ、ActiveMQ、Kafka特点生产者不知道消费者是谁消息可以持久化支持发布/订阅2基于日志的消息系统Kafka 是代表消息追加到分区日志消费者按偏移量读取优点持久化可重放高吞吐支持多消费者演化消息格式需要兼容性用 Schema Registry 管理模式3消息队列的演化挑战生产者升级后消费者可能还没升级需要向前/向后兼容死信队列处理无法解析的消息四、兼容性详解1. 向后兼容Backward Compatibility新代码读旧数据容易实现新代码知道旧格式例子新增字段新代码读取旧数据时用默认值2. 向前兼容Forward Compatibility旧代码读新数据困难旧代码不知道新字段需要旧代码能忽略未知字段例子旧代码读取含新字段的数据忽略新字段3. 兼容性实践1字段增删新增字段必须可选或有默认值删除字段只能删可选字段且不能复用字段 ID重命名字段在 Avro 中用别名在 Protobuf 中改注释2类型变更尽量避免如必须考虑兼容性如 int → long 通常安全3模式演化策略完全兼容新旧代码可互读向后兼容新读旧向前兼容旧读新破坏性变更需要版本化或迁移五、本章的核心思想总结主题核心观点编码格式语言内置不推荐文本可读但低效二进制紧凑但需模式Thrift/Protobuf字段 ID 标识支持增删字段适合 RPCAvro写入/读取模式分离最灵活适合动态模式数据流数据库、服务、消息队列各有演化挑战兼容性向后兼容易向前兼容难是分布式系统长期可维护的关键三个贯穿全书的判断编码格式决定演化能力好的格式让字段增删不影响旧代码坏的格式让每次变更都成为破坏性升级。数据流模式影响兼容性策略数据库需要长期兼容RPC 需要版本化消息队列需要 Schema Registry。兼容性是分布式系统的隐形基础设施没有它滚动升级、微服务、多版本共存都无从谈起。六、这一章在全书中地位第四章是连接单机与分布式的桥梁第二章数据模型不同模型对应不同的编码需求文档模型天然用 JSON关系模型用二进制行格式第三章存储与检索存储引擎决定数据在磁盘上的编码第五章数据复制复制的是编码后的数据兼容性影响复制协议第十一章流处理Kafka 的消息格式和 Schema Registry 直接依赖本章知识一句话概括本章数据编码是数据系统的语言。选择什么格式决定了系统能否平滑演化理解兼容性才能设计出经得起版本迭代的分布式系统。二进制格式Thrift、Protobuf、Avro在紧凑性和演化能力上优于文本格式而 Avro 的写入/读取模式分离是模式演化最灵活的方案。