系统设计 Lab
Ride matching 的瓶颈在于高频吞下司机 GPS、并快速回答 nearest-driver 查询,而不在请求量本身。
调节有多少司机在线、每个司机上报 GPS 的频率、ride-request 速率、搜索半径,以及服务多少个城市。设计会从单个 service 逐步演进到专门的 location-ingest 路径、用于 nearest-driver 查询的 in-memory geospatial index、durable 的 trip-state store,以及按 geo 分片加 surge pricing。
分步讲解
一步一步把它想清楚
要点
常规演进场景
从左到右点击,就是预设的演示路径。每张卡片都会改变 workload 输入。
推荐形态
当前架构路径
Clients
司机 app 在线时每隔几秒上报一次自己的位置
乘客 app 请求和最近的可用司机进行 match
Ingest / gateway
API gateway 给乘客做鉴权,并把 match 请求路由到 matching service
Location ingest 吸收司机 GPS 更新的 firehose,并喂给 geo index
Geo + matching
Geo index 把司机的实时位置保存在 geohash/quadtree/S2 cell 里,用于 nearest 查询
Matching service 查 index 拿候选,把乘客和司机配对起来
Trip state
Trip-state store 持久化追踪每段行程,从 requested 到 matched 到 en route 再到 complete
Geo shards 按地理把司机和行程分片,让每个 region 独立扩展
Pricing / analytics
Surge engine 在 matching 热路径之外,按区域计算 surge 倍率
Event stream 收集行程和位置事件,供离线的 analytics 与 ML 使用
瓶颈
Location 写入吞吐
Geo-query 负载
Geo-index 内存
Matching + trip-state QPS
跨城市 / shard 压力
为什么会变
决策权衡
有出处支撑的规则
这些是模型背后那些经得起时间考验的 system design 论断。而 slider 的具体阈值,则被刻意标注为教学用的假设。
Uber 用 S2 geospatial cell 把乘客匹配给司机
Uber 用层级化的 cell(H3,构建在 Google S2 之上)给整个世界建 index,让 nearest-driver 查找变成只扫一小撮 cell 的有界 scan。
Uber EngineeringGeohashing 把临近搜索变成 prefix range scan
把经纬度编码成 geohash,让邻近的点共享同一个 prefix,于是一次半径查询就变成对 in-memory sorted-set 的一小组 range 读取。
Redis Docs高频 location ingest 是一个 write-heavy 的 streaming 问题
持续涌入的逐司机 GPS 事件,最好用一个 partitioned log 来吸收,把 ingest 与 index 和下游 consumer 解耦开。
Apache KafkaQuadtree 能高效回答 2D 的 nearest-neighbour 查询
Quadtree 递归地划分空间,于是一次区域查询只访问和搜索区域有重叠的 cell,而不是每个点都看。
Wikipedia教学用假设
- Location ingest 的写入速率按「在线司机数 × GPS 上报频率」建模;真实系统会对更新做 batch 和 dedup。
- 单节点的 ingest、geo-query 和 trip-state 预算是保守的教学数字,不是厂商上限。
- Geo-query 代价随搜索半径和 cell 密度增长;in-memory index 的内存随在线司机数增长。