Tesla Robotaxi 自駕叫車 (Design Tesla Robotaxi App)

一句話定位 + TL;DR(含 35 分鐘心智時鐘)

設計 Tesla Robotaxi 叫車 App:乘客輸入起點/目的地拿 車資預估 → 確認後 叫車 → 系統把乘客與 附近可用的自駕車(AV)配對派車前往接送。本質是 即時地理配對 + 強一致派遣 系統(Uber/Lyft 的近親,但司機換成自駕車隊)。
招牌取捨:配對牽涉 海量位置寫入(1,000 萬 AV × 每 5 秒一次 ≈ 200 萬 writes/sec)與 強一致派遣(同一台 AV 不可同時被指派給多個行程、同一行程不可被多台 AV 同時指派)。設計核心是 位置走 Redis GEO(in-memory + 地理指令)叫車事件走 queue 緩衝尖峰配對狀態集中放 Redis(matching state + per-ride lock)最終靠 Ride DB 的 partial unique index 兜底——用「集中共享狀態 + 唯一約束」換 stateless worker 之下的正確性。
配速(35 min 預算):~5 min 釐清需求 → ~4 min 容量估算 → ~3 min API + ~4 min 資料模型 → ~6 min 高階架構(4 條 flow)→ ~9 min deep dive(位置/GEO、queue 防丟、1 ride 1 AV、1 AV 1 ride)→ ~3 min 擴展瓶頸 → ~1 min 收尾。開場先一句話複述題目、宣告配速、把「車資預估怎麼算」「H3/S2 自製地理索引」等細節標記為待會 deep dive 或 black box——宣告配速本身就是高分動作。

1. 釐清需求與範圍 (Clarify & Scope ~5 min)

開場後先問清楚邊界,別急著畫架構。叫車題的關鍵是先鎖定「配對」是主戰場、把「車資怎麼算」推成 black box。建議問題:

釐清問題 為何要問 / 影響到哪個決策 本題假設(依來源)
配對要多快回應成功/失敗? 決定是否需 in-memory 地理索引、是否上 queue 1 分鐘內完成配對成功或失敗
一致性要求多強? 決定要不要 lock / 唯一約束 / 交易 強一致:1 AV : 1 Ride(雙向不可重複指派)
規模多大?同時多少 AV / 尖峰多少請求? 直接驅動位置寫入估算與 queue 決策 同時 1,000 萬 AV、尖峰單社區 10 萬請求
AV 位置多久更新一次?要保留歷史嗎? 決定位置儲存選型(in-memory vs 持久 DB) 每 5 秒一次;位置是 ephemeral,不留歷史
車資預估要不要算?多複雜? 避免掉進 ML/pipeline 兔子洞 視為 black box(實務需 offline pipeline)
AV 怎麼跟後端通訊? 決定派遣命令的協定(push 而非 poll) 持久連線(gRPC 雙向 stream / WebSocket)
AV 可以拒絕行程嗎? 決定 dispatch 是否需 retry 下一個 candidate 可以(如 LOW_BATTERY → 換下一台)
defer 話術

車資怎麼精準計算 我先標記成 black box——實務上它需要 offline data pipeline + online service 餵即時供需資料,但那不是本題的決勝點。我們先把它當成 Ride Service 內回傳一個 Fare 的函式,這樣能先把 配對與派遣 這條主線串起來。」把非核心細節推遲,保住敘事連貫。

2. 功能性與非功能性需求 (FR / NFR 與設計北極星)

設計北極星(NFR 直接導出後續所有選型的判準):

NFR 導出的設計北極星 後續對映選型
低延遲配對(<1 min) proximity search 不能 full scan、不能打慢速 DB Redis GEO(in-memory 地理索引)
海量位置寫入(200 萬/s) 不能用一般 DB 承接(上限 ~10k/s)、且不需歷史 in-memory store(Redis)只存最新位置
強一致(1 AV : 1 Ride) stateless worker 之間要共享配對狀態 + 最終要有唯一兜底 Redis matching state + per-ride lock + DB unique index
高吞吐量、尖峰不丟 Ride 接單與配對要解耦,要有緩衝吸收尖峰 Ride Service → Queue → Matching Service
AV 拒絕可換車 dispatch 要能對下一個 candidate retry(同步回饋好寫) MS↔AV / MS→RS 用 RPC(retry loop 易實作)
一句話北極星

「這是一個 即時地理配對(位置寫密集)+ 強一致派遣(狀態共享)+ 尖峰不丟(queue 緩衝) 的系統」——記住這三組詞,後面每個選型都能從它推回去。

3. 容量估算 (Back-of-Envelope ~4 min)

估算的目的是 驅動位置儲存與 queue 決策(估算 → 選型的 hand-off)。回連 02-Distributed-Systems/05-Numbers-to-Know

位置寫入流量(決定性數字)

同時在線 AV ≈ 10,000,000(1,000 萬)
每台 AV 位置更新間隔 = 5 s
位置寫入 QPS = 10,000,000 ÷ 5 = 2,000,000 writes/sec(200 萬)

對照一般 DB 寫入上限

一般關聯式 DB 單節點寫入吞吐 ≈ 10,000 writes/sec(量級)
200 萬 / 1 萬 = 200 倍 → 一般 DB 直接不可行
即便水平擴展也成本高到不合理 → 必須換 in-memory store

叫車尖峰流量

單社區尖峰 ≈ 100,000(10 萬)ride requests 同時湧入
→ 若 Ride Service 同步等配對完成才回覆,極易被打爆 / 丟請求
→ 需要 queue 把「接單」與「配對」解耦

下表位置 QPS 為由「在線 AV 數 ÷ 更新間隔」算出的 推導值;DB ~10k/s 上限為量級 推導值(來源以此論證為何要換 in-memory);10 萬尖峰為來源直接給出的輸入。

維度 數字 對後續決策的意義
位置寫入 QPS ≈ 2,000,000 /sec(推導值) 遠超一般 DB(~10k/s)→ 位置改用 Redis in-memory
DB 寫入上限 ≈ 10,000 /sec(量級推導值) 證明位置不能放一般 DB;硬擴展成本不合理
尖峰叫車並發 ≈ 100,000 /社區(來源輸入) 接單與配對 解耦、中間插 queue 吸收尖峰
位置資料性質 ephemeral,只留最新 不需歷史 → in-memory 可接受(丟失靠 RDB/AOF persistence)
估算的最大收穫

200 萬 writes/sec 這個數字就是整題的轉折點:它一口氣淘汰「位置放一般 DB」,逼出 in-memory store(Redis)只存最新位置。能把「為什麼一般 DB 不行(量級對比)」講清楚,比直接說「我用 Redis」更高分。對照 06-Database-Tech/01-Database 的寫入特性與 04-Database-Indexing/05-Geospatial-Index 的地理查詢限制。

4. 用到的積木 / Building Blocks Used

架構即目錄:先給導覽圖,再展開。下表每個決策都對映一個既有 concept note(無 alias 連結);本表即「本案例練到哪些積木」的單一事實來源(single source of truth)。

設計決策 積木 在本題的作用(≤1 行)
容量估算驅動選型 02-Distributed-Systems/05-Numbers-to-Know 算出 200 萬 writes/sec,逼出 in-memory 位置儲存
地理鄰近查詢 04-Database-Indexing/05-Geospatial-Index B-tree 不適合多維座標,需專門地理索引做 proximity search
位置 in-memory 高頻寫入 + GEO 07-Caching-Storage/04-Redis Redis 存最新位置、GEOADD/GEOSEARCH 做鄰近查詢、RDB/AOF 防丟
配對狀態跨 worker 共享 07-Caching-Storage/03-Distributed-Cache matching state(candidates/cursor/status)集中放 Redis 共享
尖峰緩衝、接單與配對解耦 09-Messaging-Coord/02-Queue Ride 事件入 queue,吸收 10 萬尖峰,Matching Service 慢慢消費
尖峰不丟請求 / 不因 MS crash 丟 12-Ops-and-Reliability/03-Reliable-Delivery queue 持久化 + at-least-once 消費,保證 ride event 不遺失
多 worker 更新同一 ride state 09-Messaging-Coord/04-Distributed-Lock per-ride lock,確保同時只有一個 worker 更新該 matching state
防止重複指派的根因(競爭) 12-Ops-and-Reliability/01-Dealing-With-Contention dispatch 競態(timeout 後雙 ACCEPT)是典型 contention 問題
1 AV : 1 Ride 最終兜底 05-Database-Advanced/01-Transactions Ride DB ACID + partial unique index 保證唯一 active ride/AV
唯一性靠索引兜底 04-Database-Indexing/01-Indexing-Overview rides(av_id) partial unique index:只一個 update 成功
AV ↔ 後端持久連線 01-Networking/05-Realtime-Protocols AV 與 Gateway 持久驗證連線(gRPC stream / WebSocket)回傳位置
同步派遣命令 / retry 回饋 03-API-Design/04-RPC-and-gRPC MS↔AV、MS→RS 用 gRPC,retry loop(換下一台)易實作
派車成功即時通知乘客 10-Design-Patterns/05-Real-time-Updates 把 matched AV 位置/車牌即時推給乘客 App
AV 大量連線分散 01-Networking/06-Load-Balancing AV Gateway 前置 LB,分散 1,000 萬條持久連線

5. API 設計 (API Design ~3 min)

由 FR 驅動。乘客面是 REST(理據見 03-API-Design/02-REST);AV 面是 gRPC(持久連線 + 雙向命令,見 03-API-Design/04-RPC-and-gRPC)。

角色 Method / 介面 Path / message 用途 重點
乘客 POST /fare 取得車資預估 body { pickupLocation, destination }Fare
乘客 POST /rides 依報價發出叫車 body { fareId }Ride(觸發配對)
AV gRPC(server→AV) DispatchCommand 派遣命令 { ride_id, pickup+destination ... }
AV gRPC(AV→server) DispatchDecision AV 接受 / 拒絕 { ride_id, decision: ACCEPT|REJECT, reason }
AV gRPC bidi stream(持續) location/status updates 定期回傳位置與狀態 走持久連線,AV Gateway 轉發給 Location Service
細節話術

乘客先 POST /fare 拿到帶 fareId 的報價,確認後POST /rides { fareId }——兩步式讓「報價」與「下單」分離,後端可在 /rides 才真正觸發昂貴的配對流程;fareId 也綁定了報價快照,避免下單時價格漂移。

6. 資料模型與儲存選型 (Data Model & Storage Choice ~4 min)

本題有 三種狀態,各有最適儲存:持久行程(Ride DB)最新位置(Redis GEO)配對中間狀態(Redis matching state)。分開選型是核心。

Ride DB(關聯式,ACID)
--------------------------------------------------
ride_id     PK
rider_id    乘客
av_id       指派到的 AV(配對前為 NULL)
fare_id     對應報價快照
status      REQUESTED | DRIVER_ASSIGNED | IN_PROGRESS | DONE | FAILED
pickup, destination, created_at ...
--------------------------------------------------
關鍵:partial unique index 保證 1 AV : 1 active ride
CREATE UNIQUE INDEX uniq_active_ride_per_driver
  ON rides(av_id)
  WHERE status IN ('DRIVER_ASSIGNED','IN_PROGRESS');

Redis 位置(in-memory,承高頻寫)
--------------------------------------------------
雙向對應(更新時要能從舊位置移除 driver):
  encoded_location → [driver_ids]   (GEO 索引,供 GEOSEARCH)
  driver_id        → encoded_location(反查,更新時清舊位置)
--------------------------------------------------

Redis matching state(in-memory,跨 worker 共享)
--------------------------------------------------
Key:   match:ride:<ride_id>
Value: { candidates: [v1,v2,v3,...],
         cursor: 0/1/2...,
         status: "SEARCHING" | "DONE" | "FAILED" }

儲存選型:

狀態 / 資料 選擇 理由
行程(持久) 關聯式 DB(ACID) 強一致兜底(partial unique index)、需 transaction 保證狀態轉移正確
AV 最新位置 Redis(in-memory + GEO) 200 萬 writes/sec 一般 DB 扛不住;位置 ephemeral 不需歷史;GEO 指令做鄰近查詢
配對中間狀態 Redis(共享 K-V) stateless worker 需共享 candidates/cursor/status;天生短命、要快
一般 DB 存位置 ❌ 不選 寫入上限 ~10k/s,比需求低 200 倍;B-tree 不適合多維地理座標

→ 積木:行程強一致見 05-Database-Advanced/01-Transactions04-Database-Indexing/01-Indexing-Overview;位置與配對狀態見 07-Caching-Storage/04-Redis;地理查詢限制見 04-Database-Indexing/05-Geospatial-Index

邊界

「Redis 存位置」靠的是 位置 ephemeral(只留最新) 這個前提。一旦需求變成「保存歷史軌跡做計費 / 重播」,就得再加一條 持久寫入路徑(位置事件入 09-Messaging-Coord/03-Kafka 落地)——那是另一個系統,本題刻意不做。

7. 高階架構 (High-Level Design ~6 min)

把積木串成端到端四條 flow,再看 scaled 圖。

(A) 報價 flow(fare estimate)

Rider App
  │  POST /fare { pickup, destination }
  ▼
Ride Service
  │ 1. 計算 estimated fare(black box → 實務需 offline pipeline)
  │ 2. 在 DB 建立 fare 紀錄(保存報價快照)
  ▼
回傳 Fare(含 fareId)

(B) 叫車 flow(request ride)

Rider App
  │  POST /rides { fareId }
  ▼
Ride Service
  │ 1. 在 ride table 建立紀錄(status=REQUESTED)
  │ 2. 發佈 ride event 到 Queue,立即回 200(不等配對完成)
  ▼
回傳 Ride(接單即完成,配對非同步)

(C) 配對 flow(matching)

AV ──持久連線(gRPC stream)──► AV Gateway ──► Location Service ──► Redis(位置, GEOADD)
   (每 5 秒回傳 location/status;更新時用 driver_id→loc 反查清舊位置)

Queue ──ride event──► Matching Service (多個 stateless worker)
  │ 1. GEOSEARCH 取附近可用 AV → candidates 清單
  │ 2. 在 Redis 建 matching state { candidates, cursor:0, status:SEARCHING }
  ▼
進入派遣 flow (D)

(D) 派遣 flow(dispatch + 通知)

Matching Service worker
  │ 取 per-ride lock → 讀 matching state → 對 candidates[cursor] 發 DispatchCommand(gRPC)
  ▼
AV ──DispatchDecision──►
  ├─ REJECT/TIMEOUT → 取 lock、cursor++、dispatch 下一個 candidate
  └─ ACCEPT → 取 lock、確認 status 仍為 SEARCHING → 設 status=DONE
       │
       ▼
  通知 Ride Service(RPC)→ DB 寫 ride(av_id, status=DRIVER_ASSIGNED)
       │   └─ partial unique index 兜底:同一 av_id 只有一個 update 成功
       ▼
  Ride Service 即時通知乘客:matched AV 位置 / 車牌(見 10-Design-Patterns/05)

(E) Scaled 架構(端到端)

                  ┌─────────────── Rider 側 ───────────────┐
   Rider App ──► Load Balancer ──► Ride Service (stateless, N台)
                                       │ POST /rides
                                       ▼
                              ┌──────────────────┐
                              │      Queue        │ ← 吸收 10 萬尖峰、防 MS crash 丟單
                              └────────┬─────────┘
                                       ▼
                       Matching Service (stateless workers, M台)
                         │ GEOSEARCH        │ 共享配對狀態 + per-ride lock
                         ▼                  ▼
                 ┌──────────────┐   ┌────────────────────┐
                 │ Redis(位置GEO)│   │ Redis(matching state)│
                 └──────▲───────┘   └──────────┬─────────┘
                        │ GEOADD               │ RPC 更新
                        │                      ▼
   AV ──持久連線──► AV Gateway ──► Location     Ride DB (ACID)
   (1,000 萬條, 前置 LB)  Service               partial unique index
                                               (1 AV : 1 active ride)

關鍵 hand-off:Ride Service 接單即入 queue 立刻回(配對非同步)→ Matching worker 消費、GEOSEARCH 找車、Redis 共享狀態 + lock 派遣Ride DB unique index 做最終一致性兜底即時通知乘客。位置寫入這條(AV→Gateway→Redis GEO)與配對主線解耦。

8. 深入探討 (Deep Dives ~9 min)

不重複原則(link-don't-restate)

通用機制(Redis 原理、queue 投遞語意、distributed lock 實作、transaction 隔離)只在 concept note 解釋;這裡只講「本題怎麼用、選哪個、為什麼」。任一機制超過 ~3 行就砍掉改連結。本題無 home note 的新材料(Redis GEO 指令、matching state cursor、partial unique index)才在此 inline 教學並登記到 new_concepts_introduced

8.1 海量位置寫入 + 高效鄰近查詢

問題:1,000 萬 AV、每 5 秒更新 → ≈ 200 萬 writes/sec,且要能對乘客位置做 proximity search
為何是瓶頸:(1) 一般 DB 寫入上限 ~10k/s,差 200 倍,硬擴展成本不合理;(2) 對 lat/long 查詢若無優化要 full table scan + 逐一算距離,數百萬車時極慢;B-tree 是一維索引,不適合多維地理座標,proximity search 仍差。

新材料:Redis GEO 指令(本案例首次 introduce → 已登記 new_concepts_introduced)

  • GEOADD:把 (driver_id, lat, long) 加入一個 geo 索引(底層用 geohash → sorted set)。
  • GEOSEARCH:給一個中心點 + 半徑 / 範圍,回傳範圍內成員——這就是 proximity search。
  • 本題需 雙向對應encoded_location → [driver_ids](供查找)與 driver_id → encoded_location(反查)。後者是為了 位置更新時先把 driver 從舊位置移除,否則同一台車會殘留在多個格子。

方案 做法 優點 缺點
A. 一般 DB + lat/long 索引 B-tree on lat/long 簡單、持久 寫入扛不住(10k/s);B-tree 不適合多維 → 查詢慢
B. Redis GEO(本題選) GEOADD/GEOSEARCH,只存最新位置 in-memory 扛高頻寫;GEO 指令高效 記憶體有上限、故障可能丟資料(靠 RDB/AOF)
C. 自製 H3/S2 地理索引 cell hierarchy + in-memory(Uber/Waymo) production 級、最高效 實作成本高,面試非必要

選擇與理由:選 B(Redis GEO)。位置是 ephemeral(只需最新),正好契合 in-memory;GEO 指令讓 proximity search 高效且好實作。丟失風險用 RDB(snapshot)/ AOF(append log) persistence 緩解。
誠實補充(來源 Note):200 萬 writes/sec 下 單一 Redis 撐不住,即使 Redis Cluster 也需 數十個 shard;真實世界(Uber/Waymo)多半 自製 H3/S2 + in-memory 地理索引服務。但面試裡 Redis GEO 已是完全可接受的答案,因為重點是 配對架構,不是地理索引底層優化——點到為止即可,別掉進 C 的兔子洞。
→ 積木:07-Caching-Storage/04-Redis04-Database-Indexing/05-Geospatial-Index

8.2 尖峰期間如何不丟任何叫車請求

問題:尖峰 / 大型活動單社區可能 10 萬請求同時湧入;又要避免 Matching Service instance crash / restart 導致行程遺失
為何是瓶頸:若 Ride Service 同步等配對跑完才回覆,配對是慢流程(要 GEOSEARCH、逐一 dispatch、等 AV ACCEPT),尖峰下會塞爆、丟請求;MS 直接從 RS 同步接也會被打爆。

方案 做法 取捨
A. RS 同步呼叫 MS 等配對完成 POST /rides 內聯做完配對才回 簡單;但尖峰塞爆、RS 可用性綁死在 MS 上、MS crash 直接丟單
B. RS → Queue → MS(本題選) RS 收到確認叫車後 只發事件到 queue 立即回 多一個元件;但 queue 吸收尖峰、解耦可用性、持久化防丟

選擇與理由:在 RS 與 MS 之間插 queue 當緩衝層。RS 收到確認叫車後 只發事件、立即完成 request——配對結果不影響 RS 可用性。MS 從 queue 消費 ride events,慢慢做配對。queue 的 持久化 + at-least-once 也解決「MS crash/restart 丟行程」:事件還在 queue 裡,重啟後繼續消費。
通訊協定細節MS → RS 更新 ride state 用 RPC(gRPC)而非再走 queue——因為配對是個 retry loop(dispatch 失敗就換下一個 candidate),同步回饋在 retry loop 裡更好寫;而 RS → MS 用 queue 是為了吸收尖峰。兩段刻意用不同機制。
→ 積木:09-Messaging-Coord/02-Queue12-Ops-and-Reliability/03-Reliable-Delivery03-API-Design/04-RPC-and-gRPC

8.3 如何避免同一筆行程被多台 AV 指派(1 Ride : 1 AV)

問題:MS 依序對 candidates dispatch;若對 V1 的 dispatch timeout,MS 已改 dispatch V2,但 V1 與 V2 的 ACCEPT 同時到達,誰算數?
為何是瓶頸:為了水平擴展,MS 設計成 stateless 多 worker——發 dispatch 的 worker 與收 accept 的 worker 可能不是同一個,無法靠單機記憶體判斷狀態。這是典型 12-Ops-and-Reliability/01-Dealing-With-Contention 競態。

新材料:matching state + cursor(共享狀態機)

把配對狀態 集中放 Redismatch:ride:<ride_id>{ candidates, cursor, status }),所有 worker 共享。cursor 指向「目前嘗試到第幾個 candidate」,statusSEARCHING → DONE/FAILED 的狀態機。

做法(per-ride lock + 狀態檢查)

  1. 某 worker 算出 candidates,在 Redis 建 matching state,cursor=0status=SEARCHING
  2. candidate[0] REJECT/TIMEOUT → 另一個 worker 可重讀 state、cursor++、dispatch 下一個。
  3. per-ride lock:對每個 ride 用一把鎖,同時只有一個 worker 能更新該 state(避免並發更新 corrupt 狀態)。
  4. worker 收到某 AV 的 ACCEPT 時,先取 lock,確認 status 仍為 SEARCHING,才把 status 設為 DONE
  5. 由 3+4 保證 只有一個 worker 能成功把 status 設成 DONE → 同一 ride 只指派給一台 AV。

選擇與理由:狀態不能放 worker 記憶體(stateless + 多 worker),故 集中放 Redis;並發更新用 per-ride lock 串行化;"先檢查 SEARCHING 再設 DONE" 是 compare-and-set 語意,讓晚到的 ACCEPT 失敗。這正是本題 core_tradeoff 的一半。
→ 積木:09-Messaging-Coord/04-Distributed-Lock07-Caching-Storage/03-Distributed-Cache12-Ops-and-Reliability/01-Dealing-With-Contention

8.4 如何避免同一台 AV 被指派給多筆行程(1 AV : 1 Ride)

問題:不同 ride 的候選清單可能重疊,例如 R1→[V1,V2,V3]R2→[V1,V3,V5]。若兩個 ride 的 worker 同時 dispatch 給 V1,且 因網路問題或 AV bug,V1 同時接受了 R1 與 R2,怎麼辦?
為何是瓶頸:這個競態 跨越兩筆不同的 ride state,8.3 的 per-ride lock 管不到(它只鎖單一 ride)。需要一個 以 AV 為維度 的最終兜底。

新材料:partial unique index(部分唯一索引,最終兜底)

CREATE UNIQUE INDEX uniq_active_ride_per_driver
  ON rides(av_id)
  WHERE status IN ('DRIVER_ASSIGNED','IN_PROGRESS');

它強制:所有 active(已指派 / 進行中)的 ride 中,av_id 必須唯一WHERE 子句讓已完成 / 失敗的歷史 ride 不佔用唯一性(同一台車一天可跑很多趟)。

做法:依賴 Ride DB 的 ACID + 上述 partial unique index。當兩個 RS worker 嘗試把不同 ride 指派給同一 av_id只有一個 INSERT/UPDATE 會成功,另一個違反 unique constraint 失敗 → 失敗的 worker 發出「指派失敗」事件,觸發 re-match(重新配對)。
選擇與理由:跨 ride 的競態無法靠 Redis lock 收斂,最終一致性交給 DB 的原子唯一約束——這是強一致最可靠的兜底,且不需分散式交易。這是本題 core_tradeoff 的另一半:Redis 管「配對過程」的快與共享,DB 管「最終結果」的對。
→ 積木:05-Database-Advanced/01-Transactions04-Database-Indexing/01-Indexing-Overview

9. 擴展與瓶頸 (Scaling & Bottlenecks ~3 min)

主動找出系統會先在哪裂開並補強。

瓶頸 / 風險 補強手段 積木
位置寫入 200 萬/s 壓垮單 Redis Redis Cluster 分片(數十 shard);極限時走自製 H3/S2 地理索引服務 07-Caching-Storage/04-Redis04-Database-Indexing/05-Geospatial-Index
尖峰 10 萬叫車塞爆 Ride Service queue 緩衝 + RS 接單即回;MS workers 水平擴展消費 09-Messaging-Coord/02-Queue02-Distributed-Systems/03-Scalability
MS instance crash / restart 丟行程 queue 持久化 + at-least-once;matching state 在 Redis,重啟可續 12-Ops-and-Reliability/03-Reliable-Delivery
1,000 萬條 AV 持久連線壓垮 Gateway AV Gateway 水平擴展 + 前置 LB 分散連線 01-Networking/06-Load-Balancing01-Networking/05-Realtime-Protocols
熱點社區(單區 10 萬請求)GEOSEARCH 變慢 該區 位置 shard 細分、candidate 數量上限、配對退避 04-Database-Indexing/05-Geospatial-Index
Redis 故障導致最新位置/配對狀態遺失 RDB/AOF persistence;matching state 可重建(重新 GEOSEARCH);DB 為真相來源 07-Caching-Storage/04-Redis05-Database-Advanced/01-Transactions
drive 的關鍵句

「系統會 先在位置寫入路徑裂開(200 萬/s 遠超單 Redis)→ 所以位置層要 Redis Cluster 分片,甚至自製地理索引;其次是 尖峰叫車,靠 queue 把接單與配對解耦吸收。配對正確性不是『擴展』問題而是『一致性』問題,靠 Redis lock + DB unique index 兩層解。」先點最可能裂開處再補強。

10. 取捨總表與收尾 (Trade-off Recap & Wrap-up ~1 min)

決策點 選擇 放棄了什麼 為什麼值得
位置儲存 Redis(in-memory + GEO) 持久性 / 歷史軌跡 200 萬 writes/s 一般 DB 不可行;位置 ephemeral 不需歷史
地理索引深度 Redis GEO(不自製 H3/S2) production 級極致效能 面試重點是配對架構,GEO 已足夠,避免兔子洞
接單 vs 配對 Queue 解耦(接單即回) 同步回配對結果的簡單 吸收 10 萬尖峰、解耦可用性、防 MS crash 丟單
配對狀態 集中放 Redis + per-ride lock stateless 單機記憶體的簡單/快 多 worker 共享、1 ride 不被多 AV 搶(招牌取捨上半)
1 AV : 1 Ride Ride DB partial unique index 兜底 純 Redis 自己收斂跨 ride 競態 跨 ride 競態 + AV/網路異常的強一致真相(招牌取捨下半)
MS→RS / MS↔AV 通訊 RPC(gRPC) 全用 queue 的一致 retry loop(換下一台 candidate)同步回饋好寫
收尾話術

「整題單一錨點是 『過程靠 Redis、結果靠 DB』的兩層一致性:配對是個多 stateless worker 的 retry loop,所以把 matching state 集中放 Redis 並用 per-ride lock 讓同一 ride 不被多 AV 搶;但跨 ride 的競態(同一 AV 被多 ride 搶)Redis 收不住,就交給 Ride DB 的 partial unique index 做原子兜底。位置那條因為 200 萬 writes/s + ephemeral,走 Redis GEO,並坦白單 Redis 不夠、實務要分片或自製地理索引——但面試裡 GEO 已足夠。尖峰用 queue 解耦 防丟單。」引用既有決策用連結,不重新推導。

後續追問演練 (Interviewer Follow-ups — "What if…")

為什麼 AV 位置不放在一般關聯式 DB?

位置更新時為什麼要先把 driver 從舊位置移除?

queue 在這裡解決了哪兩個問題?

dispatch V1 timeout 後 MS 改派 V2,結果 V1、V2 的 ACCEPT 同時到達,怎麼保證只有一台被指派?

),V1 因 bug 同時 ACCEPT R1 與 R2,怎麼辦?

流量放大 100x(某城市瞬間爆量),架構哪裡先撐不住?怎麼補?

Redis 故障,最新位置與 matching state 全沒了,系統還能恢復嗎?

為什麼 MS→RS 用 RPC,而 RS→MS 用 queue?不能統一嗎?

partial unique index 的 WHERE status IN (...) 為什麼不可省略?

面試時要不要主動講 H3/S2 自製地理索引?

主動回想:白板重建 (Whiteboard from Scratch)

不看上文,照下面提示把整個設計重建一次。每格都該能 30 秒內口述。

常見陷阱 (Exam Traps for This Problem)