架构
$DynamoDB$ 表是 条目 ($Item$) 的集合, 每个条目是属性 ($Attribute$) 的集合
每个条目有一个主键 ($Primary Key$), 主键的模式 ($Schema$) 在表创建时指定
- 模式 1: 分区键 $Partition \ Key$
- 模式 2: 分区键 + 排序键 $Sort \ Key$
Hash(Partition_Key)
和 $Sort \ Key$ 决定条目的存储位置, 但是排序键必须不同
支持辅助索引 $Secondary Index$, 一个表可以有一个或多个辅助索引, 使用辅助索引加速非主键属性的查询
支持 $ACID$ 事务, 保证条目之间的一致性, 原子性, 隔离性, 持久性 且不影响 可伸缩性, 可用性, 性能特征
可预测性
读容量单位 $RCUs$ 和写容量单位 $WCUs$ 显式指定表所需的吞吐量
- 对于大小不超过 $4kb$ 的条目, 一个 $RCU$ 每秒执行一个强一致的读请求
- 对于大小不超过 $1kb$ 的条目, 一个 $WCU$ 每秒执行一个标准写请求
准入控制
初步准入控制
根据大小划分分区
- 热分区: 当应用程序的访问并不均匀时, 会出现类似热点的热分区问题
- 吞吐量稀释: 当对一个分区进行操作时, 对其他分区的吞吐量分配会减少
初步改进
- 突发: 每个分区存两个令牌桶: 分配桶和突发桶, 每个节点存一个令牌桶
请求到达当前分区: 优先扣除分配桶和节点的桶, 如果不够的话在突发桶和节点桶仍然有剩余的情况下才能进行突发, 写请求还要检查其他分区和副本的令牌桶 - 自适应容量: 监控每个分区的流量, 使用比例控制算法调整该分区的吞吐量
突发只能处理短时峰值: 依赖节点冗余的吞吐量实现突发
自适应容量是被动的: 已经造成了不可用才知道去调整吞吐量
全局准入控制 $GAC$
每个请求路由器维护一个本地令牌桶, 定期与 $GAC$ 通信来补充令牌
$GAC$ 实例负责估算全局令牌消耗, 动态分配令牌
另外, 分区级令牌桶也被保留下来, 防止每个应用程序或客户端垄断一个节点的全部资源
消耗均衡
容量消耗均衡
节点的吞吐量超过最大吞吐量的一定比例, 就会向自动管理服务报告
自动管理服务会找到一个没有该分区副本的新存储节点
消耗划分
分区消耗的吞吐量超过某些阈值, 就会对该分区进行分割
- 在单条目频繁访问的分区
- 按顺序访问键范围的分区: 可能会破坏有序性从而增加复杂度
按需分配
收集读写信号, 根据消耗的容量进行分配
持久性和正确性
- 硬件故障: 多副本保证可用性
- 静默数据错误: 使用大量的校验和来保证数据正确
- 软件错误: 使用大量的故障注入和压力测试来防止出现软件错误
$DynamoDB$ 还会持续验证静态数据防止出现意料之外的错误
备份和恢复: 通过预写日志构建, 还支持时间点恢复
可用性
- 写和一致性读可用性: 使用 $Paxos$ 管理
- 故障检测: 当 $Follower$ 无法与 $Leader$ 通信时, 询问副本确认 $Leader$ 确实挂掉再开始选举
- 测量可用性: 全局表 99.99% 可用性和区域表 99.99% 可用性
- 持续监控, 并尝试自动修复
- 从客户端观测: 有两组客户端可用于观测
部署
每次部署之前都会进行组件级别的升级和降级测试
通过读写部署来处理 旧代码理解不了新代码增加的部分 的问题
- 部署软件来处理新的部分
- 所有节点都能处理新部分之后再部署
所有部署都是从点到面的
外部服务依赖
$DynamoDB$ 使用一个稳定的静态设计, 一个依赖挂掉, 整个系统仍然能正常工作
$DynamoDB$ 定期异步刷新缓存, 在预设的一段时间内继续使用缓存的结果, 此时不依赖外部服务, 缓存还可以消除对下层的调用从而改善响应时间
元数据可用性
请求路由器中最重要的数据是 表的主键到存储节点之间的映射
$DynamoDB$ 构建了一个名为 $MemDS$ 的内存分布式数据存储以低成本的方式降低元数据扩张和可用性风险