DynamoDB 的請求路由是如何工作的
你發出的每一次讀取或寫入,都會先命中一隊無狀態的請求路由器。 路由器對你的 做雜湊,把雜湊值對映到擁有該鍵資料的儲存節點, 再把請求轉發過去。正是這一跳,讓一次按鍵查詢的成本保持一致 —— 無論表裡存的是一千個項還是十億個項。
DynamoDB 的請求路由是如何工作的?
DynamoDB 會將每個請求都經由一隊無狀態的請求路由器轉發:路由器對你的 做雜湊,把雜湊值對映到擁有該分割區的那一個儲存節點,再把讀取或寫入轉發過去。路由是鍵雜湊的純函式,因此一次查詢的成本保持一致 —— 無論表裡存的是一千個項還是十億個項。
- 請求路由器是入口大門。 它是一隊無狀態的路由器,接收你的 請求,對分割區索引鍵做雜湊,然後把它路由到持有該分割區的儲存節點 —— 無需掃描,也無需瞭解整張表。
- 分割區索引鍵決定一切。 路由是分割區索引鍵雜湊的
純函式 —— 同一個鍵始終路由到擁有它的分割區,所以
GetItem是 O(1),而不是 O(表大小)。 - 一主兩副。 一次寫入落在該分割區的主節點上, 主節點在法定多數(三個副本中的兩個)持久化之後確認寫入。
- 糟糕的鍵會擊垮這套設計。 一個低基數或 鍵會把 流量灌向單個節點 —— 路由本身沒問題,問題出在你的鍵上。
先從路由要解決的問題說起
從 SQL 過來,你腦中浮現的是一個查詢規劃器:它讀取統計資訊、挑選索引, 也許還會掃描。成本隨它觸及的資料量而增長。這套模型並不適合 一個必須在任意規模下都以個位數毫秒作答的鍵值儲存。
DynamoDB 的答案是把單項查詢變成一次直接定址,而不是一次 搜尋。分割區索引鍵不是一個你用來過濾的列 —— 它是一個雜湊 函式的輸入,用來算出_資料在物理上存放在哪裡_。沒有統計資訊,也沒有規劃器。
這就是你從關係式思維遷移出來時所接受的權衡:你放棄了 臨時查詢的靈活性,換來了常數時間的定址。
認識請求路由器
請求到達時,並不會直奔儲存。它會先命中一個請求 路由器 —— 一隊無狀態、可橫向擴充套件的路由器,擋在整個服務的最前面。 (USENIX ATC '22 的 DynamoDB 論文描述了這隊請求路由器。)
路由器做三件事,自身不持有任何資料:
- 對照 IAM 鑑權與授權該請求。
- 對分割區索引鍵做雜湊以找到擁有它的分割區。
- 把請求轉發給該分割區的儲存節點。
因為路由器是無狀態的,服務會在負載上升時增加更多路由器。它們中沒有一個是 瓶頸,也沒有一個是單點故障 —— 這正是 2007 年 Amazon Dynamo 論文 圍繞原始系統所構建的那個特性。
跟隨一次讀取穿過路由器
設想一張記錄無人機機隊遙測資料的表。項以 DroneId(分割區
鍵)和 ReadingTs(排序索引鍵)為鍵,帶有 BatteryPct、AltitudeM 等屬性。
你請求某架無人機在 6 月 23 日的讀數:
PK = "DRONE#A19F"
SK begins_with "2026-06-23"
下面是路由器對它做的處理。下方的引導文字自上而下地追蹤這個請求 —— 請把它當作一條向下流動的路徑來讀。
路由器對 DRONE#A19F 做雜湊,把它對映到擁有該鍵的分割區,再把
讀取轉發給該分割區的主儲存節點,由它返回項。
關鍵洞見:雜湊指向的是_一個_分割區,而無論這張表有 多少個分割區。路由器從不檢視其他分割區,所以增加無人機 —— 以及分割區 —— 並不會拖慢這次查詢。
弄清分割區到底是什麼
分割區是儲存與吞吐量的單位。每個分割區都有上限(大約
10 GB 加上讀/寫容量的一個固定切片),當分割區超出任一上限時,DynamoDB 就會
拆分它。帶有某個給定分割區索引鍵的每個項一開始都落在一個
分割區上;隨後的按熱度拆分可以按排序索引鍵範圍切分這個集合(除非
有 LSI 或單調遞增的排序索引鍵把它釘住),這正是讓針對
同一個分割區索引鍵的 Query 保持廉價的原因。
每個分割區都被複制到分散在多個可用區的三個儲存節點上:一個主節點 和兩個副節點。
| 節點角色 | 處理 | 可提供的一致性 |
|---|---|---|
| 主節點 | 所有寫入;強一致讀取 | 強(能看到自己的最新寫入) |
| 副節點 | 最終一致讀取;故障轉移 | 最終(可能落後於主節點) |
一次寫入交給主節點,主節點在法定多數(三個 副本中的兩個)持久化之後確認寫入。一次讀取會被路由到主節點, 因此它反映最新寫入。一次讀取可能由 一個尚未追上的副節點來提供 —— 成本減半,可能讀到舊值。
點名一個陷阱:熱分割區索引鍵
路由的好壞,取決於你的分割區索引鍵。雜湊會把鍵均勻鋪開,所以如果 你的鍵具有高基數且流量均勻,負載就會分散到所有 節點上。破壞其中任一屬性,你就會得到一個熱分割區。
假設你用 Region 而不是 DroneId 作為那張遙測表的鍵。現在
us-east-1 裡的每架無人機都共享同一個分割區索引鍵 —— 於是它們的讀寫會雜湊到同一個
鍵空間槽位,全都堆到同一個項集合上。路由器仍在完美地
履行它的職責;只是你把整個機隊都灌進了單個分割區的容量裡。
你沒法盯著路由器挑選節點,但你_可以_設計出路由良好的鍵。
當你在
運算式構建器裡構建一個鍵條件時,你放在
PK = … 左側的分割區索引鍵,正是路由器將要雜湊的那個值 —— 讓這個
值保持高基數,正是讓讀取落在不同節點上的關鍵。
這如何回扣到你的訪問模式
請求路由正是讓單表設計
規則不容商量的那套機制:你圍繞分割區索引鍵建模,因為分割區索引鍵
_就是_地址。這也是為什麼 Query 勝過 Scan ——
Query 經由路由器命中一個分割區,而 Scan 會依次走遍每一個
分割區。
次要索引擁有各自的分割區和各自的路由:一個 GSI 由它自己的分割區索引鍵路由,與基表的分割區索引鍵無關, 這正是為什麼即便表不熱,GSI 也可能變熱。
後續步驟
設計出能路由到多個節點、而非單個節點的鍵。在
運算式構建器裡草擬 PK = … 條件,看清究竟是哪個值
會被雜湊,然後下載 DynoTable,針對你自己的
表執行那些查詢,看清每個鍵條件究竟返回什麼。