跳至主要內容
ESC
經典架構拆解 — 第 2/6 篇

經典架構拆解 · 02 — Uber 即時派單架構

經典架構拆解 · 02 — Uber 即時派單架構

上一篇看完 Netflix 的全球串流架構,這次我們切到完全相反的另一端:即時地理配對。Netflix 的痛點是「把大檔案推到使用者附近」,這是一個讀多寫少、可以慢慢預熱的問題;Uber 剛好相反,它要在一秒內,把某個 GPS 座標的乘客,和半徑 500 公尺內最適合的司機配對起來,而且那個座標每幾秒就在動。

這是兩種規模差很多的問題,但 Uber 的解法對 PCA 考生特別有用,因為它把**地理分片(geo sharding)事件驅動工作流(event-driven workflow)**這組經典組合,示範得乾乾淨淨。


為什麼值得拆解

Uber 的派單系統,遠遠不只是「一個大型的 matching engine」。它要同時扛住三件事:

  • 即時位置流:全球所有司機每 4 秒上報一次 GPS。
  • 地理查詢:以乘客為中心,在地圖上找出符合車種、評分、路況的候選司機。
  • 配對工作流:司機接受、拒絕、取消、到場、上車、下車——每一步都可能失敗,需要一個就算中途斷線也能接著跑的狀態機。

這三件事,剛好對到 PCA 考試裡最容易被忽略的三個能力:水平分片策略、低延遲 NoSQL 選型、長流程(long-running workflow)建模。下面我們就順著這三條線拆下去。


商業規模與壓力

先看一下這套系統到底在扛多大的量。根據 Uber 2024 年 Q4 財報,Uber 平台在 2024 年全年完成約 113 億次行程(Trips),月活躍平台使用者約 1.71 億。光是把這些行程跑起來,背後的派單系統就得處理每秒數百萬次的地理查詢,遇到尖峰時段(跨年夜、機場外大型活動散場),還得再吞下 10 倍以上的流量。

數字大家看過就好,重點是這種量級會逼出哪些設計上的硬限制:

  • 單一資料庫表撐不住:一張「司機位置」表如果沒分片,查詢幾分鐘就會把整個系統拖垮。
  • 地理查詢不能用 SQL WHERE distance < X:就算掛了空間索引,跨 region 一路掃過去的延遲也沒辦法接受。
  • 每一趟行程都是一個長流程:可能跨 30 分鐘、橫跨好幾個城市,中間還會碰到網路斷線跟伺服器重啟。

這三條,後面每一條都對應到一個 PCA 會考的決策,我們一個個看。


架構演進簡史

這套架構不是一次到位的,它是被規模一路逼出來的。看一下時間軸,你會發現每一個里程碑背後幾乎都是「上一代撐不住了」:

年份里程碑意義
2014–2015舊版 Dispatch(DISCO)用 Google S2 空間索引城市數量增加後,單一 DB 成為瓶頸
2015自研 Ringpop 一致性雜湊(consistent hashing)讓同一城市的請求永遠落到同一組節點,避免跨節點查詢
2016–2017Cadence workflow 開源把派單、費用結算等長流程從「ad-hoc state machine」改為宣告式 workflow
2018H3 地理索引開源把地球切成六角形格子(hexagon),取代經緯度矩形分片
2019Temporal 從 Cadence 分支(後繼專案)原 Cadence 作者另起爐灶,成為日後的主流 workflow engine
2021+全站事件匯流改用 Apache Kafka 為主幹解耦下單、配對、計費、通知等子系統

把上面的決策畫成一張圖,派單的資料流大致是這樣走的:

GPS 讀寫

乘客 / 司機 App

API Gateway

派單服務

(geo-sharding · H3)

配對引擎

(供需匹配)

位置資料庫

(低延遲 NoSQL)

Kafka 事件主幹線

計費 / 通知 / ETA

Uber 即時派單:請求經 API Gateway 進派單服務,用 geo-sharding(H3)配上低延遲位置庫做供需配對,事件走 Kafka 串起計費/通知/ETA。


核心技術決策

決策為何這樣選替代方案與為何沒選
用 H3 六角形格子做地理分片六角形的相鄰距離一致,比經緯度矩形更公平;可分多層級,從城市到街角都能表達純經緯度矩形:邊界扭曲(極區特別嚴重)、相鄰距離不均
Ringpop 一致性雜湊定位服務節點當節點增減時,只有 1/N 的請求需要重新路由,不用全體重分配傳統 sharding key:節點擴容時大量熱點漂移,連線風暴
Cadence/Temporal 管理派單流程派單是跨小時、跨服務、跨失敗的長流程,用一般訊息佇列無法描述「下一步等多久才 timeout」自建 state machine:每種失敗路徑都要自己寫重試與 timeout 邏輯
位置資料用 in-memory NoSQL + 分層持久化司機位置寫入頻率極高、讀取延遲要求壓到幾十毫秒(業界常見量級,非官方公布值),用 SSD 資料庫撐不住直接寫 PostgreSQL:寫入熱點會馬上打爆
事件匯流用 Kafka下單、配對、計費需要強順序保證;消費者可以獨立擴容RabbitMQ 或純 REST:無法保證城市內事件順序或回放能力

這張表第一列那個「先分片、再選 DB」的順序,我自己在一個小很多的案子上踩過教訓。當時做的是一個外送店家的「附近可接單騎手」清單,量跟 Uber 差了不知道幾個數量級,一開始就是 PostgreSQL 一張表配 PostGIS,ST_DWithin 查半徑,跑得好好的。後來騎手位置改成高頻上報,寫入一密集,那張表的索引就開始抖,查詢延遲從幾十毫秒飆到好幾百。最後的解法不是換更猛的資料庫,而是先把地圖切成網格、查詢只在「乘客所在格 + 相鄰格」裡找——查詢範圍一框住,原本那台 PostgreSQL 又夠用了。這跟 Uber 用 H3 框查詢範圍是同一個思路,只是我的版本土炮很多。那次之後我才真的懂這張表想講的事:到了某個量級,瓶頸不在你選哪個 DB,而在你有沒有先把查詢範圍關進一個 shard 裡


如果用 GCP 重新蓋

那如果今天要在 GCP 上重蓋一套,會長怎樣?這部分對考試特別有用,因為 PCA 考的就是「同一個業務問題,在 GCP 上你會挑哪些服務」。一個個對過去:

  • 即時地理索引: Bigtable 作為 geo index 主力,使用 H3 cell ID 當作 row key,利用其 lexicographic 排序特性做範圍查詢;熱資料另用 Memorystore(Redis)加速。
  • 配對與 dispatcher 服務: GKE(regional)部署 dispatcher,搭配 Cloud Service Mesh(前身為 Anthos Service Mesh)做區域親和性路由,對應 Ringpop 的角色。
  • 長流程工作流: Cloud Workflows 適合輕量編排;真的要處理 Cadence 等級的持久狀態機,可自架 Temporal on GKE,因為 Temporal 就是 Cadence 的後繼專案。
  • 事件匯流: Pub/Sub(或 Kafka on GKE / Confluent Cloud)。需要嚴格順序與長期保留時用 Kafka;純解耦用 Pub/Sub。
  • 跨 region 交易資料: Spanner 存行程、帳單、付款紀錄,利用它的 multi-region 強一致能力。
  • 離線分析: BigQuery 處理歷史行程與供需熱力圖。

這張對應表不是要你背 Uber 用什麼,而是練那個反射:看到「即時地理 + 高頻寫入 + 範圍查詢」就想到 Bigtable + 合理的 row key,看到「跨小時、會失敗、要重試」就想到 workflow engine。練到這個反應變直覺,PCA 一整類系統設計題的底層思維就通了。


這套架構在 PCA 怎麼考

把上面那些技術決策翻成考場語言,大致就是三類題:地理分片、事件驅動解耦、長流程工作流。下面兩個 box 把「題目會怎麼出、選項陷阱長怎樣、看到什麼字要往哪靠」整理給你。

📝 考場提點

地理分區與資料分片這類題,題幹通常會塞「即時位置」「附近的 X」「每秒大量讀寫」「查詢延遲要低」這些字,核心問的其實是「怎麼讓查詢不要跨 region 掃整張表」——這正是 H3 + 一致性雜湊在做的事。GCP 對應到 Bigtable 當 geo index,關鍵字一出現就往這邊靠。兩個常見陷阱:①選項給你「Cloud SQL / PostgreSQL 加空間索引」,看起來能跑,但題目只要強調「全球規模」「每秒數百萬次」,這個就是來騙你的——關聯式資料庫扛不住這種寫入熱點。②看到「低延遲」就無腦選 Memorystore(Redis)——Redis 是加速熱資料沒錯,但它不是你的主索引,主力存放還是 Bigtable,別把快取層當成資料庫層。Row key 設計也愛考:把 H3 cell ID 當 row key 是為了用 Bigtable 的字典序做範圍查詢,要是看到「用隨機 UUID / 時間戳當 row key」的選項,那是反模式,直接刷掉。

📝 考場提點

事件驅動長流程工作流常常被放在同一份 case study 裡,但要選的服務不一樣,別搞混。事件驅動的訊號是「子系統要解耦」「計費、通知、ETA 各自獨立演進」「要能回放」——GCP 預設答案是 Pub/Sub;但題目只要特別點名「嚴格順序」或「長期保留 / 可重播歷史」,就要往 Kafka(GKE 自架或 Confluent Cloud)靠,這是 Pub/Sub 跟 Kafka 的分水嶺。長流程工作流的訊號完全不同:「一趟流程跨幾十分鐘到幾天」「中間會失敗、要重試」「要記得跑到哪一步」——這時候選 Cloud Workflows(輕量編排)或自架 Temporal(重量級持久狀態機,它就是 Cadence 的後繼專案)。最大的陷阱是拿錯工具配錯需求:用 Pub/Sub 去硬撐一個「要記住中間狀態、會 timeout 重試」的長流程,或反過來用 workflow engine 去做單純的 fan-out 解耦——兩種都是會被扣分的選型。一句話記法:要解耦、要順序,找訊息系統;要記狀態、要重試,找 workflow engine。


常見誤解

這幾個是我看別人(包括早期的自己)最常想歪的地方:

  • 「Uber 的派單就是 PostGIS + 空間索引」 —— 一般應用這樣做沒問題,但到 Uber 這種規模就不行了。重點不在選哪個 DB,而是先把「查詢範圍」框在同一個 shard 內,資料庫選型反而是次要的。
  • 「Cadence 就是 cron job」 —— Cadence/Temporal 是持久化的 workflow engine,可以讓一個函式跑好幾天、就算中途重啟也接著跑。這跟 cron 的 fire-and-forget 差很多。
  • 「一致性雜湊只是負載均衡技巧」 —— 在 Uber 的架構裡,它同時也是資料親和性(data locality)機制:把同城市的請求導到同一組節點,快取命中率就跟著衝上去。

第二點我特別有感,因為親手摔過。以前做一個訂單處理的流程——下單、扣庫存、通知出貨、等物流回拋狀態——一開始我就是一個排程腳本配幾張狀態表硬幹,自己記「這筆走到哪一步了」。平常沒事,直到一次半夜部署,服務在「扣完庫存、還沒通知出貨」中間被重啟,那批訂單就卡在不上不下的狀態,隔天得人工撈出來一筆筆對。後來改用 workflow engine,那種「跑到一半重啟、醒來自己接著跑」的能力一上來,整類問題直接消失。這個經驗讓我永遠記得 cron 跟 workflow engine 的差別:前者是 fire-and-forget,後者是會幫你記住進度的。我那個是幾百筆訂單的小場面,Uber 是每天上億趟行程、每趟都是一個可能斷在任何一步的長流程,但會痛的點一模一樣,只是它的代價大上幾億倍。


來源與延伸閱讀


下一篇:從「即時地理系統」切換到另一個經典問題 —— Stripe 的 API 冪等性設計,看他們如何讓「重複付款」在架構層面就不可能發生。

🎯 換你練習

想動手設計類似系統?到 架構師設計工作坊 用這套思考步驟走一遍,也可以對照 PCA 五大案例庫 的官方題目練手。

經典架構拆解 — 2/6 完成 查看系列全覽 →

留言討論

徽章解鎖!