书 · 2017-03 · 马丁·克莱普曼(Martin Kleppmann)

《设计数据密集型应用》

计算机与 AI书单第 17 名

一句话结论

理解数据系统,关键是弄清每种保证靠什么实现、付出什么代价,以及它是否解决了你的实际问题。

值不值得读完整本?

值得读完整本。它把数据库内部结构、分布式系统和数据处理串成了一套判断方法,能帮助你解释架构选择背后的原因。

  • 先读第1—4章,建立可靠性、负载、数据模型、存储和兼容性这些基本概念;已经熟悉的查询语法与编码格式细节可以略读。
  • 第5—9章值得最慢地读,重点区分复制与分区、事务隔离与副本一致性,以及两阶段提交与共识。
  • 第10—12章应连着读:先理解批处理如何派生数据,再看流处理怎样持续更新结果,最后判断作者提出的系统组合方式。
  • 配合小练习:画出一次复制延迟、一次并发写入和一次消息重试的执行顺序,再说明各自可能破坏什么保证。为学科考试准备时,不建议跳过任何一整部分。

核心观点

1. 先定义需求与故障,再选择技术

系统设计要围绕可靠性、可扩展性和可维护性展开,并把它们落实为具体负载、故障场景和演进要求。某个产品跑得快,并不等于整个应用设计得好。

为什么:相同的请求数量,可能因为读写比例、数据分布或用户之间的连接关系而产生完全不同的成本。平均响应时间又会掩盖少数很慢的请求,而一个用户操作往往要等待多个服务共同完成。硬件损坏、软件错误和人的误操作需要不同的防护措施。系统还会长期经历新旧程序共存,因此数据编码能否前后兼容,也直接影响升级的风险。

2. 数据模型与存储结构,都在分配读写成本

关系、文档和图模型适合表达不同的数据关系;B树、LSM树和列式存储则适合不同的访问负载。选择时要追问:哪些操作变便宜了,哪些工作被转移到了别处?

为什么:把经常一起读取的数据放进同一文档,可以减少查询时的拼接,但共享信息被重复保存后,更新就更难保持一致。关系模型通过连接组合数据,图模型则把关系遍历作为直接操作对象。在存储层,LSM树通过缓冲和顺序写入提高写入效率,同时承担合并和读取多个结构的成本。B树以页为单位组织和更新数据,列式存储则通过只读所需列和压缩,减少分析查询的数据搬运。

3. 复制与分区扩展能力,也引入新的协调问题

复制把同一份数据放到多台机器,分区把不同数据分给不同机器;两者解决的问题不同,也经常一起使用。增加机器后,必须重新处理数据新鲜度、写入冲突、热点和请求路由。

为什么:异步复制让写入不必等待所有副本,却使读请求可能看到旧数据,故障切换时也可能丢失尚未复制的写入。允许多个节点接受写入,可以减少部分等待,但并发修改随后需要检测和合并。分区能分散数据与负载,却无法自动分散一个特别热门的键。范围分区方便范围查询,哈希分区通常更利于打散键,而二级索引和跨分区查询又会增加通信成本。

4. 事务把复杂故障交给数据库,但必须看清隔离级别

事务的价值是让应用少承担一部分崩溃恢复和并发控制工作。仅仅知道数据库支持 ACID,还不足以判断业务约束是否安全。

为什么:原子性让一组操作可以整体提交或撤销,避免应用逐一修补执行到一半的状态。隔离性则决定并发事务能够观察和影响彼此到什么程度。快照隔离可以提供稳定的读取视图,却仍可能让两个事务根据相同旧状态,分别修改不同记录,合起来破坏约束。可串行化要求结果等价于某种逐个执行的顺序,但实现它需要付出限制并发、等待或中止重试等成本。

5. 分布式正确性必须建立在不确定性之上

网络延迟、进程暂停和时钟误差使节点无法直接知道整个系统的真实状态。线性一致性、因果顺序和共识等概念,是针对不同问题提供明确保证的工具。

为什么:请求超时既可能意味着操作没有执行,也可能意味着执行成功而回复丢失,因此重试可能重复产生效果。机器时钟会漂移或跳变,单靠时间戳不能可靠判断任意两个操作的真实先后。需要唯一领导者或统一决定时,系统必须使用协调协议,不能让每个节点独立猜测。共识协议在其故障模型和多数派等条件下维护一致决定,但网络分区时,一些节点可能无法继续推进操作。

6. 用可重放的数据流组织派生数据,并把正确性做到端到端

批处理、流处理、搜索索引和缓存,都可以理解为从已有数据计算新表示。保留输入、明确处理顺序和管理副作用,可以让系统更容易恢复、重建和检查。

为什么:输入保持不变、计算能够确定重现时,失败任务可以重跑,错误结果也能在修正程序后重新生成。把数据库变化作为事件传播,可以减少应用分别写数据库和索引时产生的不一致窗口。批处理面对有边界的输入,流处理面对持续到达、可能乱序或迟到的事件,因此必须区分事件发生时间与处理时间。重试虽然有助于恢复,却可能重复修改外部状态,所以处理进度与输出需要协调提交,或依靠幂等和去重。消息系统内部的保证只有覆盖到最终业务效果,才构成应用需要的正确性。

反对意见

关系模型的能力不应与具体 SQL 产品混为一谈(C. J. Date)

Date 在《SQL and Relational Theory》第三版中强调,关系模型是逻辑基础,SQL 及其产品实现并不等同于这个模型;书中也讨论关系值属性等表达方式。这给本书按关系、文档、图并列比较的实用路线提出一个追问:某种便利究竟来自另一种模型,还是来自更好的接口与实现?这是理论优先级上的不同,并非针对本书的直接批评。

超大规模应用应从不依赖跨实体事务出发(Pat Helland)

Helland 在《Life beyond Distributed Transactions: an Apostate’s Opinion》中认为,追求极大规模时,全局事务的性能成本与脆弱性会使其不实用;他主张把事务限制在实体内部,实体之间通过消息和业务流程协作。与克莱普曼重视通用事务抽象的立场相比,他更愿意让业务代码直接面对跨实体操作的不完整性。论文以不采用大规模分布式事务为讨论前提,不能把它当作事务无法扩展的数学证明。

MapReduce 的灵活性可能以丢失数据库能力为代价(David J. DeWitt、Michael Stonebraker)

两人在《MapReduce: A major step backwards》中批评,底层 MapReduce 编程让用户重新承担记录处理工作,并缺少成熟数据库的模式管理、索引、声明式查询和工具生态。这对本书肯定的批处理组合方式提出了具体成本问题:容错和自由处理输入的好处,是否值得额外的开发与管理负担?本书第10章已经讨论这场论争,也承认高层语言和查询优化的重要性,因此双方存在不少共识。

相关推荐

导读由 AI 根据公开资料整理,没有读取原书全文,一切以原著为准。导读 Primer 首页