拓冰建站拓冰建站
首页 / 资讯中心 / 正文

DDIA 导读(四):编码与演化

本文是《Designing Data-Intensive Applications》DDIA中文译名《数据密集型应用系统设计》第 4 章的导读。DDIA 是 Martin Kleppmann 所著的分布式系统经典本系列逐章导读把书的核心概念讲清楚。一句话主旨数据比代码活得久。应用程序会持续演进、持续发布但已经写出去的数据不会跟着升级——所以数据的编码格式必须能容许 schema 变化而容许变化靠的是向前兼容和向后兼容这两个纪律。这一章讲的是如何让数据在时间维度上活下来。核心概念拆解1. 编码格式——数据怎么变成字节数据在内存里是对象/结构体指针、引用、嵌套写到磁盘或网络时要编码serialize成字节序列读回来要解码deserialize。编码格式的核心选择磁盘/网络内存中的数据结构编码 encode解码 decode对象/结构体有指针、嵌套引用字节序列连续字节三大类编码格式格式类型人类可读Schema演化支持JSON / XML文本✅无/可选弱类型模糊、无 schema 约束Protobuf / Thrift二进制❌必须有强字段编号/optionalAvro二进制❌必须有强schema 注册表读写比对2. 语言专属格式——别用Java/Python 各有自带的序列化pickle、Java SerializableDDIA 明确警告不要用语言专属格式做持久化或跨服务通信。原因绑死语言、跨语言不可读、演化能力差、常有安全漏洞pickle 反序列化能执行任意代码。选 JSON、Avro、Protobuf 等跨语言格式。3. JSON/XML 的问题JSON 简单通用但有几个演化上的痛点类型精度不足数字与字符串可区分但整数与浮点不区分、无法指定精度且无 schema 约束解码后类型细节靠猜没有 schema 约束加字段/减字段无人挡接收方要容错处理字段可能不存在嵌套和数组表达力弱JSON 能存嵌套数组但查询语言对数组里某对象的某字段的访问能力有限冗余每条记录都重复字段名空间浪费4. Protobuf / Thrift——带 Schema 的二进制核心思想用一个独立的 schema 文件.proto描述数据结构编解码都依赖它。字段用编号标识不靠字段名。message Document { int32 id 1; // 字段编号1, 名字id string dataset 2; // 字段编号2 float score 3; // 字段编号3 optional string title 4; // 新加的字段, 旧数据没有 }为什么用编号不用名字编号是字节里的标识编号不变就不会破坏兼容。改名id → doc_id无所谓只要编号 1 不变。加新字段给个新编号旧代码读到不认识的编号直接跳过向前兼容旧数据没有新字段新代码按 optional/默认值处理向后兼容。演化的纪律✅ 加新字段给新编号✅ 删字段但编号不能复用✅ 字段改 optional❌ 改字段类型int → string 破坏兼容❌ 复用已删字段的编号旧数据里那个编号还是旧含义5. Avro——为大数据而生schema 动态比对AvroHadoop 生态有个独特设计schema 写在数据文件头里每条记录不带字段名/编号纯值。Avro 文件文件头: schema(字段名类型)记录1: 纯值 0.8,stem,...记录2: 纯值 0.5,industry,...关键机制——读写 schema 比对写数据时用 writer’s schema存进文件头读数据时用 reader’s schema当前程序的 schemaAvro 运行时比对两个 schema按字段名匹配处理差异举例旧数据用 writer’s schema有score字段无category字段新程序用 reader’s schema有score和category两个字段。Avro 按字段名匹配score读写都有 → 正常读category读有写无新程序期望 category 但旧数据没存→ 用 reader’s schema 里category的默认值没有默认值则报错Avro 不隐式填 null反过来如果旧数据有legacy_tag但新 schema 删了它写有读无 → 读取时丢弃不报错为什么适合大数据记录不带元数据极紧凑schema 演化靠字段名匹配不是编号加字段只要给默认值即可。Hadoop/Spark 生态大量用 Avro/Parquet 正因如此Parquet 是另一种独立的列式格式设计动机不同。Protobuf vs Avro——核心差别两者都解决同一个问题带 schema 的紧凑二进制 演化兼容但诞生于不同生态设计取向不同ProtobufAvro字段标识编号编号不变就兼容字段名按名匹配schema 位置独立 .proto 文件编解码双方各自持有写进数据文件头读时与 reader’s schema 比对记录冗余每条记录带字段编号紧凑但不零冗余每条记录纯值零元数据冗余最紧凑适合场景服务间通信gRPC、跨语言 API大数据文件存储Parquet/Avro 文件、批处理管道演化方式加字段给新编号旧代码跳过未知编号加字段给默认值读写 schema 动态比对如果你的工作涉及服务间通信微服务 API、gRPCProtobuf 是主流选择如果涉及大数据存储格式Spark/Hadoop 管道里的 Parquet/Avro 文件Avro 更常见。6. 向前兼容 / 向后兼容——两个方向这是全章最该记住的概念新版本加optional字段旧数据没该字段, 用默认值旧代码遇到新字段必须能跳过不崩向后兼容 Backward Compatible新代码能读旧数据向前兼容 Forward Compatible旧代码能读新数据✅ 容易做到⚠️ 需要纪律向后兼容新读旧通常容易新代码认识旧格式即可。向前兼容旧读新更难要求旧代码遇到不认识的新字段/新格式时不崩溃、能跳过。这需要编码格式本身支持忽略未知字段。双方向都需要的场景分布式系统里多个节点版本不一致时滚动升级期间新旧节点并存必须同时满足向前和向后兼容。7. 数据流的演化模式DDIA 把数据流转分三种模式每种演化的挑战不同数据流典型演化挑战数据库写入后读向前向后都要滚动升级时新旧版本并存新代码写新格式旧代码读要兼容RPC/REST服务间调用向前向后都要服务端客户端独立部署异步消息Kafka/RabbitMQ最难消息可能堆积生产者已升级消费者还是旧版消息队列的演化风险高于数据库因为消息可能在队列里躺很久跨越多个版本周期。如果消息格式body 的字段要演进生产者发新格式、消费者还没升级——这就是向前兼容问题。选 JSON body 要靠消费者忽略未知字段的人工纪律选 Protobuf/Avro 则有格式级保障。8. Schema-on-Write vs Schema-on-Read演化视角跨章回顾第 2 章提过第 4 章从演化角度再讲Schema-on-Write如 MySQL/PostgreSQL写时强制校验 schema。加列要ALTER TABLE旧数据该列填默认值。演化有明确纪律但迁移要停服或在线 DDL。Schema-on-Read如 JSON 存半结构化列、文档引擎宽松 mapping不强制校验字段有无都容错。演化灵活加字段不用改表但数据质量靠应用自律。Schema-on-Read 的演化自由是用查询表达力和数据质量保障换的——加字段零成本但查询层无法精确依赖字段结构数据可能脏。这是第 2 章 第 4 章共同揭示的权衡。问题→方案问题——数据比代码活得久schema 要变但旧数据不能重写。场景——应用滚动升级期间新旧版本并存新代码加字段、删字段、改结构。方案——编码格式支持向前/向后兼容Protobuf 靠字段编号编号不变就兼容Avro 靠读写 schema 动态比对按字段名匹配、缺字段填默认值JSON 靠接收方忽略未知字段的人工纪律。纪律越严Protobuf/Avro演化越可控自由度越高JSON 无 schema演化越靠自觉。Mermaid数据流演化模式数据库流只需向后兼容RPC/REST流向前向后都要异步消息流最难: 消息可能跨越多版本加列ALTER, 旧数据填默认值服务端客户端独立部署要双向兼容消息堆积期间生产者新格式消费者旧版下一篇第 5 章——复制。进入全书最难的第二部分分布式数据从单机怎么存转向多机怎么保持一致。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门