高级阅读约 3 分钟

DynamoDB 请求路由如何工作

你发出的每一次读取或写入都先命中一队无状态的 请求路由器。一个路由器哈希你的 partition key,把哈希映射到拥有那个 key 数据的存储节点,并把请求转发到那里。那一跳就是为什么一次 key 查找无论表装着一千个还是十亿个 item 都花同样的代价。

DynamoDB 的请求路由是如何工作的?

DynamoDB 将每个请求通过一队无状态的请求路由器,哈希你的 partition key,将哈希映射到拥有该分区的单一存储节点,然后将读取或写入转发到那里。路由是 key 哈希的纯函数,因此无论表中有一千个还是十亿个条目,单次查找的代价始终相同。

  • 请求路由器是前门。 它是一队无状态的舰队,接过你的请求,哈希 partition key,并把它路由 到持有那个分区的存储节点——不扫描,不需要全表知识。
  • partition key 决定一切。 路由是 partition key 哈希的一个纯函数。同样的 key,同样的 节点,每一次——所以 GetItem 是 O(1),不是 O(表大小)。
  • 一个主,两个从。 一次写入落在分区的主节点上,它在变持久之前跨可用区复制到两个从节点。
  • 坏 key 击败这个设计。 一个低基数或"热"的 partition key 把流量漏斗到一个节点上——路由 没问题,你的 key 才是问题。

从路由解决的问题开始

从 SQL 过来,你想象一个查询规划器:它读统计信息、挑一个索引、也许扫描。代价随它触及多少数据 而缩放。那个模型不适合一个必须在任何规模下都以个位数毫秒回答的键值存储。

DynamoDB 的答案是把单 item 查找变成一次 直接寻址,而不是一次搜索。partition key 不是你 去过滤的一个列——它是一个哈希函数的输入,那个函数算出 数据物理上住在哪。没有统计信息,没有 规划器。

那是你从关系型思维挪开时接受的交换:你放弃临时查询的灵活性,换来常数时间的寻址。

认识请求路由器

当一个请求到来,它不直接去存储。它命中一个 请求路由器——一队无状态、横向扩展的舰队, 顶在整个服务前面。 (AWS re:Invent "DynamoDB Deep Dive" 会议描述了这支前端舰队。

路由器做三件事,自己不持有任何数据:

  • 针对 IAM 认证并授权 请求。
  • 哈希 partition key 来找到拥有它的分区。
  • 转发 请求到那个分区的存储节点。

因为路由器是无状态的,服务在负载下添加更多它们。它们中没有一个是瓶颈,也没有一个是单点故障—— 正是 2007 年 Amazon Dynamo 论文 围绕原系统构建的那个特性。

跟着一次读取穿过路由器

拿一张无人机队的遥测表。item 以 DroneId(partition key)和 ReadingTs(sort key)为 key, 带 BatteryPctAltitudeM 这样的属性。

你索要一台无人机最新的读数:

PK = "DRONE#A19F"
SK begins_with "2026-06-23"

下面是路由器对它做的事。下方的引子从上到下追踪这个请求——把它读作一条向下的流。

客户端:GetItemPK = DRONE#A19F请求路由器(无状态舰队)Hash(DRONE#A19F) keyspace 槽位把槽位映射到拥有这个 key 的分区那个分区的主节点读取 itemDRONE#A19F

路由器哈希 DRONE#A19F,把它映射到拥有那个 key 的分区,并把读取转发到那个分区的主存储节点, 它返回 item。

关键洞见:哈希指向表拥有的众多分区中的 一个。路由器从不看其他分区,所以添加无人机——以及 分区——不会让这次查找变慢。

了解一个分区到底是什么

一个 分区 是存储和吞吐量的一个单元。每一个都被封顶(大致 10 GB 和读/写容量的一个固定 切片),而当一个分区长过任一限制时 DynamoDB 拆分它。带给定 partition key 的每个 item 都住在 同一个 分区上,这正是让一次对一个 partition key 的 Query 便宜的东西。

每个分区被复制到散布在多个可用区的三个存储节点:一个 和两个

节点角色处理它能服务的一致性
所有写入;强一致读取强(看到它自己的最新写入)
最终一致读取;故障转移最终(可能落后于主)

一次写入去主,它在确认持久性之前复制到从。一次 强一致 读取被路由到主,所以它反映最新写入。 一次 最终一致 读取可能由一个还没跟上的从服务——一半成本,可能陈旧。

点名自坑:一个热 partition key

路由只有你的 partition key 那么好。哈希把 key 均匀铺开,所以如果你的 key 有 高基数 且 流量均匀,负载就铺到所有节点上。破坏任一性质,你就得到一个 热分区

假设你把那个遥测按 Region 而不是 DroneId 来设 key。现在 us-east-1 里的每台无人机都共享 一个 partition key——所以它们的每一次读取和写入都哈希到 同一个 节点。路由器把它的活儿干得 完美;你只是把整个机队漏斗到了单个分区的容量上。

你看不到路由器挑节点,但你 可以 设计路由良好的 key。当你在 表达式构建器里构建一个 key 条件时,你放在 PK = … 左边 的那个 partition key 就是路由器将要哈希的确切值——把那个值保持高基数正是让读取落在不同节点上 的东西。

这如何系回你的访问模式

请求路由是让 单表设计规则不可商量的机制:你围绕 partition key 建模,因为 partition key 就是 那个地址。它也是为什么一个 Query 胜过一个 Scan—— 一个 Query 通过路由器命中一个分区,而一个 Scan 按顺序走每一个分区。

二级索引拿到它们自己的分区和它们自己的路由:一个 GSI 按它自己的 partition key 路由,独立于基表的,这就是为什么一个 GSI 可以在表不热的时候也变热。

下一步

设计路由到许多节点而非一个的 key。在 表达式构建器里勾画 PK = … 条件,精确看到哪个值被 哈希,然后 下载 DynoTable,对你自己的表跑那些 query,并看着哪些 partition key 实际承载你的流量。

更新于