从 Dynamo 论文到 DynamoDB
2007 年的《Dynamo: Amazon's Highly Available Key-value Store》论文,与 你今天调用的 DynamoDB 共享一个名字和一个目标 —— 在任意 规模下都可预测的性能 —— 但它们不是同一个系统。论文描述的是一个 内部的、最终一致的存储,由你自己运行。DynamoDB 则是一个托管服务,它 保留了经验教训,却扔掉了大部分机制。
DynamoDB 是基于 Dynamo 论文的吗?
部分是。DynamoDB 从 2007 年的 Amazon Dynamo 论文取用了它的名字和核心目标 —— 在规模上可预测的性能和高可用性 —— 并几乎原封不动地保留了哈希的思想。但它是一个不同的托管系统:论文中的向量时钟、gossip 成员管理,以及可调的读/写法定人数都已消失,被 AWS 自有的内部机制所取代。
- 论文解决的是可用性,而非易用性。 它的任务是在 假日流量高峰期间绝不拒绝一次写入,哪怕代价是返回一次陈旧的读取。
- DynamoDB 保留了形态,替换了内部机制。 按键的哈希 分区、跨可用区复制、水平扩展 —— 但冲突解决的 内脏(向量时钟、gossip、读修复)已经不在了。
- 你不再调节旋钮了。 论文里的
N、R和W变成了一个 选择:ConsistentRead为真或为假。其余由 AWS 掌管。 - 心智模型依然有回报。 了解这段血脉能解释为什么
Scan昂贵、为什么 GSI 读取会滞后 —— 两者都源自最初的设计。
论文实际上在解决什么
Amazon 的购物车不能宕机。一个在负载下拒绝 写入 —— 或在某个副本失败时阻塞 —— 的关系型数据库是无法接受的。2007 年的 Dynamo 论文选择了可用性优先于一致性:总是接受写入, 之后再调和分歧。那个取舍是下文一切的根源。
要在没有单一主节点的情况下做到这一点,Dynamo 必须自己回答两个问题: 一个键住在哪里,以及一次读取或写入生效之前必须有多少副本达成一致?
一致性哈希:一个键住在哪里
论文把每个节点放在一个哈希环上。一个键的位置是它的
键的哈希;它由顺时针方向的下一个节点拥有,并复制到随后的 N-1
个节点。添加或移除一个节点只会重新洗牌它邻居的键 —— 而非
整个数据集。这就是一致性哈希,也是 DynamoDB 几乎
原封不动保留的那个思想。
DynamoDB 仍然对你的做哈希,以决定由哪个物理分区存储
该项目。选一个低基数的分区键 —— 比如只有两个值的 STATUS
—— 那么每个具有相同值的项目都会落到同一个分区。那就是这个大坑,也是那个环的直接后果:哈希把
相同的键送往相同的归宿。
法定人数:多少副本必须一致
论文的第二个旋钮是法定人数(quorum)。有 N 个副本时,一次写入一旦
有 W 个副本确认就算成功,一次读取会咨询其中 R 个。设 R + W > N,那么任何读取
都至少与一个持有最新写入的节点重叠 —— 强一致性。把它们设得
更低,你就用新鲜度换取速度和正常运行时间。
Dynamo 运行“宽松的”法定人数:如果目标节点宕机,写入会去往一个 替身,并在之后交还(提示移交,hinted handoff)。冲突的版本会被 标上向量时钟,并在读取时由应用来调和。
DynamoDB 保留了什么、又改变了什么
DynamoDB 继承了目标和分区方式,然后删去了那些让 原始系统难以运维的部分。
| 关注点 | 2007 年 Dynamo 论文 | 今天的 DynamoDB |
|---|---|---|
| 键的放置 | 一致性哈希环 | 分区键的哈希 → 托管的分区 |
| 复制 | N 个节点,由你选择 | 3 份副本跨可用区,由 AWS 固定 |
| 一致性旋钮 | R、W 法定人数调节 | 一个标志:ConsistentRead |
| 冲突解决 | 向量时钟,读取时应用侧合并 | 区域内无需 —— 写入通过一个主副本串行化;只有全局表跨区域时才最后写入者胜出 |
| 成员管理 | 对等节点间的 gossip 协议 | 完全托管;对你不可见 |
| 多键操作 | 无 —— 纯键值 | Query、GSI、事务叠加在其上 |
论文的 API 是两个调用:get(key) 和 put(key, value)。DynamoDB 在同一个
键值内核之上添加了排序键、索引和查询 —— 这就是为什么
Query 廉价(一个分区),而 Scan 不廉价(它走遍那个环
曾经创建过的每一个分区)。
一次写入如何行进,今昔对比
下面的流程对比了论文的法定人数写入和 DynamoDB 的托管式写入。 形态相似;责任从你的代码移到了 AWS。
在论文里,你掌管法定人数的运算和合并;在 DynamoDB 里,整个下半部分
都是托管的,你只需为每个请求选择 ConsistentRead。
这段血脉在哪里渗入你的代码
最终一致性的默认设置就是论文的透出。一个全局二级 索引是异步复制的,因此一个刚写入的项目可能会有片刻从索引中 缺失 —— 同样是那笔“之后再调和”的交易,只是发生在索引 层。参见 GSI 与 LSI,了解那种滞后何时会有影响。
你可以用两种方式买回强一致性。在基表读取上使用 ConsistentRead: true
(它会路由到主副本),或者用一个 ConditionExpression 守卫一次写入,
使它只有在项目当前状态匹配时才落地。在
DynamoDB 表达式构建器里草拟一个 —— 例如
attribute_not_exists(PK),把一次 PutItem 变成只做插入的操作,即
论文中冲突检测的现代替身。
唯一要记住的一件事
论文为“永不对写入说不”而优化。DynamoDB 继承了那种
偏向,这就是为什么它的默认设置偏爱可用性,以及为什么强一致读取成本
更高。为单分区 Query 建模你的键,就像
单表设计里那样,并
只在你确实必须时才动用 Scan —— 那个环
让一次全表遍历如它听起来那般昂贵。
试用 DynoTable,浏览你的表及其 GSI,然后在 SQL Workbench 里对你自己的数据运行 JOIN 和 GROUP BY。