DDIA 读书笔记
引言
最近终于把《Designing Data-Intensive Applications》这本书读完了,说真的,这本书让我对分布式系统的理解上升了一个层次。之前虽然也在工作中接触过一些数据存储和处理的场景,但总觉得知识点零散,没有形成体系。Martin Kleppmann 这本书正好帮我补上了这块短板,把数据系统设计的方方面面串联了起来。今天就把我的读书笔记整理一下,分享给同样在分布式系统路上摸爬滚打的朋友们。
数据模型与查询语言
书的第一部分探讨了数据模型和查询语言,这是所有数据系统的基础。作者从最传统的关系型数据库讲起,然后聊到文档数据库、图数据库等各种 NoSQL 方案,最后还提到了新晋选手——关系型数据库的复兴。
我印象最深的是关于数据模型演进的讨论。作者指出,每种数据模型都有其适用场景,没有银弹。比如文档数据库适合数据结构灵活多变的场景,但涉及到多表关联查询时就比较吃力了。作者用了一个很形象的例子:
-- 关系型: 把数据拆散存储,通过 JOIN 关联
SELECT * FROM users u
JOIN posts p ON u.id = p.user_id
WHERE u.id = 123;
// 文档型: 把相关数据嵌套存储
{
"user_id": 123,
"name": "张三",
"posts": [
{"title": "第一篇文章", "content": "..."},
{"title": "第二篇文章", "content": "..."}
]
}
两种方式各有优劣,关键看你的业务场景。文档模型减少了 JOIN,但可能导致数据冗余;关系模型规范化程度高,但查询可能涉及多表关联。这个话题让我重新思考了项目中数据模型的选择,确实不能盲目追新。
存储与检索
第二部分深入探讨了数据库内部的存储引擎,这是很多开发者不太会接触到的层面,但也正是这些底层机制决定了数据库的性能表现。
索引的那些事儿
作者详细介绍了B-Tree和LSM-Tree两种主流索引结构的原理和适用场景。B-Tree 是大多数关系型数据库的标配,查询稳定,范围查找友好;而 LSM-Tree 则更适合写入密集型的场景,通过牺牲一点查询性能来换取写入吞吐量。
书中还提到了一个很实用的观点:没有免费的午餐。每种索引都会带来额外的写入开销,所以建立索引要权衡。我之前就犯过这个错误,给一个高频写入的表加了一堆索引,结果写入性能直线下降,读完这部分终于明白了原因。
日志结构存储
作者花了相当篇幅讲日志结构存储(Log-Structured Storage),这让我对 Kafka 这样的消息队列有了更深的理解。其实消息队列本质上就是一个日志的持久化存储,通过追加写入的方式实现高吞吐量。这个思路也被应用到了很多新型数据库中,比如 Cassandra 就大量借鉴了日志结构的思想。
分布式数据
这部分是全书的精华所在,也是我认为最有价值的章节。当单机无法满足需求时,我们就必须走向分布式,而分布式带来的复杂性远超想象。
复制与分区
数据复制是分布式系统的核心话题之一。作者介绍了三种主要的复制策略:1. 主从复制:一个主节点处理写入,多个从节点处理读取。简单直接,但主节点是瓶颈。
2. 多主复制:多个节点都可以处理写入,扩展性好,但冲突解决很头疼。
3. 无主复制:放弃中心化的主节点,完全对等,典型代表是 Dynamo。
每种方式都要面对复制滞后的问题——从节点的数据可能比主节点慢半拍。书中特别强调了最终一致性和线性一致性的区别,这个概念在分布式系统中非常重要,但很多开发者容易忽视。
事务与一致性
事务是数据库最古老的特性之一,但在分布式环境下,事务的实现变得极其复杂。作者详细解释了 ACID 的含义,以及分布式事务中两阶段提交(2PC)和三阶段提交(3PC)的工作原理。我个人最喜欢的是关于 CAP 定理的讨论。作者指出,CAP 定理经常被误解——它不是说系统可以同时满足一致性、可用性和分区容错性,而是说当发生网络分区时,你必须在一致性和可用性之间做选择。但实际上,分区并不总是发生,所以正常情况下系统可以同时保证 C 和 A,只有在分区发生时才需要取舍。
# 简化理解 CAP 定理
def choose_during_partition(network_partitioned: bool):
if not network_partitioned:
return "C + A" # 正常情况下可以兼得
else:
return "C or A" # 分区时必须二选一
共识算法
书中还介绍了 Raft 和 Paxos 等共识算法。讲真,这部分内容有一定难度,我读了好几遍才大致理解。简单来说,共识算法解决的问题是:在多个节点之间如何就一个值达成一致。这是分布式系统中最基础也最重要的问题之一。
Raft 算法通过领导者选举、日志复制、安全性三个机制来保证一致性。它的优势在于比 Paxos 更容易理解,所以现在很多分布式系统都采用 Raft,比如 etcd、Consul 都用 Raft 来实现高可用。
数据格式与序列化
这部分讨论了数据如何在网络上传输和如何持久化存储,涉及 JSON、Protocol Buffers、Avro 等常见格式。
作者的一个重要观点是:数据格式和编程语言是解耦的。选择一种跨语言的序列化格式,可以让你的系统更容易集成和演进。Protocol Buffers 和 Avro 都支持模式演进,这在微服务架构中特别有用——你可以独立升级服务版本而不需要一次性全部更新。
// Protocol Buffers 示例
message User {
int32 id = 1;
string name = 2;
optional string email = 3; // 可选字段,支持向后兼容
}
Avro 的优势在于它使用 JSON 定义模式,但数据本身是二进制编码,兼顾了可读性和效率。而且 Avro 在 Hadoop 生态系统中应用广泛,如果你的系统和大数据相关,值得考虑。
总结
读完整本书,我最大的收获是建立了一个系统性的分布式知识框架。以前遇到问题总是就事论事,现在能够从更高的视角去分析:这个问题属于数据模型层面还是存储引擎层面?是复制策略的问题还是共识算法的挑战?
当然,这本书也不是完美的。部分章节难度较大,尤其是后半部分的共识算法和事务章节,需要有一定基础才能很好地理解。另外,书中的例子主要基于西方互联网公司的实践,有些场景在国内的业务环境下可能不太适用。
总的来说,《Designing Data-Intensive Applications》值得每一位后端工程师认真读一读。它不会教你具体的代码怎么写,但会帮你理解背后的原理。理解了原理,才能在实际的系统设计中做出更好的决策。这本书我会放在书架显眼的位置,时不时翻出来复习一下,常读常新。
DDIA 读书笔记
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法