給定使用者位置與半徑,回傳附近的商家(餐廳、飯店、加油站…)。難點在於把二維的經緯度變成能被索引、能快速搜尋的資料。
📋 Requirements
- 功能:依位置(latitude/longitude)與半徑回傳商家;店家可新增/刪除/更新商家(隔天生效即可);顧客可看商家詳情。
- 半徑:使用者從固定選項挑選(0.5km、1km、2km、5km、20km),最大 20km。
- 非功能:低延遲、資料隱私(GDPR/CCPA)、高可用與可擴展(尖峰流量)。
- 規模:1 億 DAU、2 億家商家。
🧠 Core Ideas
TIP
核心是「把二維經緯度映射到一維、切成小格建索引」,讓半徑搜尋不必掃全表;再靠讀多寫少的特性重讀取路徑(replica + 快取)。
讀取量極高(搜尋附近 + 看詳情),寫入量低(增修商家不頻繁)。這種讀多寫少的負載,用關聯式資料庫(MySQL)是合適選擇,主要兩張表:business 表與 geospatial 索引表。
⚖️ Reference Design
高階架構
- 負載平衡器:單一 DNS 進入點,依 URL 路徑把請求路由到對應服務。
- LBS(location-based service):系統核心,只讀、無狀態、QPS 高 → 容易水平擴展。
- 商家服務:處理增修商家(寫,QPS 低)與看詳情(讀,尖峰高)。
- 資料庫叢集:主從設定,主庫寫、多 replica 讀;因商家不需即時更新,複寫延遲造成的不一致可接受。
地理空間索引方案比較
- 二維搜尋:直接用經緯度範圍畫方框查詢。索引只能加速單一維度,兩個 dataset 還要 intersect,效率差。
- 均勻切格:世界切成等大方格。問題是商家分佈不均(都市 vs 沙漠),且找相鄰格麻煩。
- Geohash:遞迴把世界切成四象限、逐 bit 縮小,把二維座標編成一維字串。共用前綴越長越接近;用長度 4~6 對應不同半徑。邊界案例需一併查相鄰格。實作簡單、更新容易、支援半徑查詢,但格子大小固定、無法隨密度調整。
- Quadtree:記憶體內樹狀結構,遞迴切成四象限直到每格商家數不超過門檻(如 100)。能隨人口密度調整格子、支援回傳 k 個最近商家;但需建樹、更新較複雜、伺服器啟動要花時間建樹。
- Google S2:記憶體內方案,用 Hilbert curve 把球面映射到一維,擅長 geofencing 與彈性的 region cover。
面試建議選 geohash 或 quadtree(S2 較難口頭講清楚)。
資料表擴展
- business 表:資料大,依 business_id 分片,負載均勻、易維護。
- geospatial 索引表:整份資料其實不大(quadtree 約 1.71GB,geohash 類似),能裝進單機工作集。瓶頸是讀取量而非容量,因此優先加 read replica分擔讀取,而非直接分片(分片邏輯要進應用層,複雜且無必要)。
- geohash 索引表用
(geohash, business_id)複合主鍵、一列一商家,增刪都無需鎖。
快取
- 先問「真的需要嗎」:資料集小、可全進工作集,查詢幾乎跟記憶體一樣快;讀取瓶頸也能靠 replica 解。要導入前需 benchmark 與成本分析。
- 快取鍵不用原始座標(手機座標不精確、會微幅漂移);用 geohash,位置小變動仍對應同一格。
- 快取兩類資料(cache-aside):
geohash → 格內 business ID 清單、business_id → business 物件。以三種精度(geohash_4/5/6)預先計算,全球約需 5GB。 - 因「隔天生效」的約定,快取可用每晚工作更新。
🔑 Takeaways
- 先認清「讀遠多於寫、資料集不大」→ 設計重心是地理空間索引與讀取路徑(replica、選配快取)。
- 二維範圍查詢與均勻切格都不夠好;主流是 geohash / quadtree(各有取捨:geohash 簡單易更新、quadtree 能隨密度調整並支援 k-nearest)。
- 擴展 geo 索引別急著分片——資料能進單機工作集時,先用 read replica。
- LBS 無狀態易擴展,並可跨 region/AZ 部署以降延遲、分散負載、符合當地隱私法規。
No notes yet — jot your takeaways or Q&A here.