深入探索数据系统的本质、权衡与演进。
包括:数据模型、分布式一致性、共识算法、批处理与流处理架构。
这三个词不仅是流行语,更是评估系统优劣的核心维度。本章建立了全书的思维框架,定义了我们在谈论软件质量时到底在谈论什么。
Fault (故障): 系统的一部分偏离其标准(如硬盘坏了)。
Failure (失效): 系统作为一个整体停止向用户提供服务。
可靠性就是容忍故障,防止其演变成失效。
不是"X是否可扩展",而是"如果负载增加,性能如何变化?"。
关键指标:P99/P999 尾部延迟 (Tail Latency),直接影响用户体验的最差情况。
1. 可操作性: 方便运维团队监控和修复。
2. 简单性: 消除意外复杂性 (Accidental Complexity),使用良好抽象。
3. 可演化性: 易于修改以适应新需求。
数据模型不仅仅是软件开发的一个环节,它决定了我们思考问题的方式。关系模型、文档模型和图模型各有优劣,关键在于数据之间的关系如何处理。
数据被组织成元组(Rows),集合为关系(Tables)。
优势: 强大的 Join 支持,多对多关系处理得当。
特点: Schema-on-write(写时模式),强制结构化。
数据存储为类 JSON 文档。
优势: 数据局部性好(一次读取整个树),Schema-on-read(读时模式,灵活)。
劣势: Join 支持弱,多对多关系处理复杂。
顶点 (Vertices) 和 边 (Edges)。
优势: 针对高度互联的数据(如社交网络、推荐引擎)。
查询语言:Cypher (Neo4j), SPARQL, Datalog。
数据库的核心是两个操作:保存数据和找回数据。本章深入探讨了日志结构与页面结构存储引擎的底层原理,以及 OLTP 与 OLAP 的本质区别。
基于 SSTables (排序字符串表)。
流程: 写入内存 MemTable -> 刷入磁盘 SSTable -> 后台合并压缩 (Compaction)。
优点: 顺序写入,高吞吐量。适合:LevelDB, RocksDB, Cassandra。
将数据分割成固定大小的页 (Pages, 通常 4KB)。
原理: 通过指针树状查找,就地更新 (Update-in-place)。
地位: 关系型数据库事实上的标准。
按列而非按行存储数据。
优势: 极高的压缩率(位图编码、行程编码),CPU 缓存友好,适合聚合查询。
场景: 数据仓库 (Data Warehouse)。
应用不断变化,数据格式也随之变化。本章讨论如何通过编码格式(JSON, Avro, Protobuf)处理向前和向后兼容性,实现系统的平滑演进。
Avro, Protocol Buffers, Thrift。
相比 JSON/XML,更小且解析更快。关键在于它们依赖 Schema,允许省略字段名,强制类型检查。
Backward (向后): 新代码读旧数据。
Forward (向前): 旧代码读新数据(通过忽略未知字段实现)。
这对零停机部署至关重要。
1. 数据库: 写入者编码,读取者解码。
2. RPC/REST: 客户端与服务端通信。
3. 异步消息: 节点间通过消息队列解耦。
复制是为了高可用、容错和降低延迟。但当数据在多个节点间复制时,"一致性"成为了最大的挑战。
所有写操作发给 Leader,Leader 将变动流发送给 Followers。
同步复制: 强一致,但写性能受限于最慢节点。
异步复制: 高吞吐,但主节点挂掉会丢失数据。
每个数据中心有一个 Leader。
优点: 容忍数据中心故障,写性能好。
缺点: 必须解决写冲突 (Write Conflicts)。
客户端写入多个节点。没有单点故障。
依赖 Quorum (法定人数) 机制:W + R > N。
使用 Read Repair 和 Anti-entropy 修复数据。
当数据量大到单机无法承载时,我们需要分区(分片)。核心问题是如何均匀分布数据以及如何处理查询路由。
像百科全书一样,A-C卷,D-F卷。
优点: 范围扫描高效。
缺点: 容易产生热点 (Hot Spots),如按时间戳分区会导致写入集中。
对 Key 计算 Hash 值,根据 Hash 分区。
优点: 数据分布均匀,消除热点。
缺点: 丧失范围查询能力 (Cassandra 结合了两者)。
基于文档 (Local): 每个分区维护自己的索引。写快,读需要 Scatter/Gather。
基于词条 (Global): 全局索引也分区。读快,但写复杂(分布式事务)。
Hash % N,因为 N 变化时会导致大规模数据迁移。推荐使用固定数量分区或动态分区。事务是应用层的首选抽象,用于处理部分失败和并发问题。ACID 中的“隔离性”是最复杂也是最容易被误解的部分。
保证:无脏读 (Dirty Read),无脏写 (Dirty Write)。
实现:行锁防脏写,保存旧值防脏读。
缺点:存在不可重复读 (Non-repeatable Read)。
每个事务读取一致的数据库快照。
MVCC (多版本并发控制): 写入不阻塞读取。
解决不可重复读,但仍有写偏斜 (Write Skew)。
执行结果与串行执行完全相同。
实现方式:
1. 实际串行执行 (Redis, VoltDB)
2. 2PL (两阶段锁)
3. SSI (可串行化快照隔离)
这是全书最悲观的一章。在分布式系统中,你无法完全信任网络,甚至无法完全信任时间。我们必须假设一切都会出错。
网络可能延迟、丢包、乱序。没有上限的延迟 (Unbounded Delay)。
结论: 你无法区分节点宕机还是网络慢,只能靠超时 (Timeout) 来猜测。
NTP 可能导致时钟回拨。
Wall clock: 适合看时间。
Monotonic clock: 适合算时长。
依赖时间戳排序会导致数据丢失 (LWW 的隐患)。
节点可能处于 "Stop-the-world" GC 暂停中,以为自己是 Leader,实际上已过期。
需要 Fencing Token 来防止旧 Leader 破坏数据。
这是最难也是最核心的一章。探讨了最强的一致性模型,以及如何通过共识算法实现它。
让系统看起来像只有一个数据副本,且操作是原子的。
一旦写入成功,任何后续读操作都必须看到新值。
代价:网络分区时不可用 (CAP 的 C)。
相当于状态机复制 (State Machine Replication)。
如果所有节点按相同顺序执行相同命令,状态就是一致的。
这是 ZooKeeper/Etcd 的核心。
解决分布式系统中节点达成一致的问题。
核心机制:Epoch/Term (代/任期) + Quorum (法定人数投票)。
保证安全性 (Safety),不保证活性 (Liveness)。
离线处理大数据的基石。MapReduce 继承了 Unix 管道的哲学,虽然现在逐渐被 Spark/Flink 取代,但思想永存。
1. 输入不可变 (Immutable Input)。
2. 无副作用。
3. 简单的文本接口。
MapReduce 是分布式的 Unix 管道。
Sort-Merge Join: Mapper 排序,Reducer 合并。
Broadcast Hash Join: 小表广播到所有 Mapper 内存。
Partitioned Hash Join: 按 Key 哈希分发。
将中间状态保存在内存而非写磁盘。
将整个工作流视为一个图 (DAG) 而非独立的 Map/Reduce 步骤。
性能远超 MapReduce。
处理无界数据流。这不仅是关于实时性,更是关于如何解耦系统和处理变化。
JMS/AMQP: 消费者处理后删除消息,无序。
Log-based (Kafka): 持久化日志,消费者维护 Offset,支持重放 (Replay),有序性强。
Event Time: 事件实际发生时间。
Processing Time: 服务器处理时间。
处理乱序和滞后事件 (Straggler) 需要 Watermark 机制。
Stream-Stream: 时间窗口内的 Join。
Stream-Table: 流扩充 (Enrichment),如根据 ID 查用户信息(CDC 维护 Table)。
总结全书,提出“拆分数据库”的愿景。通过组合专门的工具来构建可靠、可维护的系统。
不存在“万能”数据库。
使用数据流 (Dataflow) 将 OLTP、索引、缓存、分析系统连接起来,就像 Unix 管道连接工具一样。
区分记录系统 (System of Record) 和衍生数据 (Derived Data)。
利用 Log-based 系统实现数据的异步传播和视图构建。
仅仅依靠数据库事务是不够的。
需要应用层的端到端校验(如 Request ID 去重)。
从“信任”转向“验证” (Auditability)。