跳到主要内容

网约车系统设计

网约车案例需要支持哪些角色与操作?​

  • 乘客: 查看附近车辆、预计到达时间 ETA 与价格,选择目的地叫车并查看司机实时位置;
  • 司机: 接受或拒绝订单,查看接客地点,开始行程并在到达后结束;
  • 质量目标: 高可靠、高可用、低延迟并可扩展,派单正确性比地图位置绝对同步更重要;
  • 扩展需求: 行程评价、支付及指标分析;
  • 核心约束: 一个司机同一时刻不能接下冲突行程,一个订单也不能被多个司机成功占有;

请求与定位上报的规模如何分别计算?​

  • 题设: 1 亿 DAU、100 万司机、每天 1000 万单,每个用户每天执行 10 次业务动作;每次记录按 400 B;
项目计算结果
业务请求1 亿 × 1010 亿/日
平均业务 RPS10 亿 / 8640011574/s,约 12K/s
每日逻辑记录10 亿 × 400 B400 GB
十年逻辑记录400 GB × 36501.46 PB
平均业务入口400 GB / 864004.63 MB/s,约 5 MB/s
平均行程创建1000 万 / 86400115.74/s
  • 假设边界: 请求不一定都永久保存,若只存行程而非完整动作事件,存储应按对应实体重算;
  • 定位补充: 若 100 万在线司机每 5 秒上报一次,就是 20 万次/s,已远大于业务请求基线;
  • 位置带宽: 位置载荷、协议开销、乘客推送及轨迹保留需要单列预算,原文的 5 MB/s 无法覆盖全部系统;
  • 地区峰值: 请求集中在城市、上下班和活动区域,全球平均值不能代表单个派单分区的容量;

行程和支付数据如何关联?​

实体关键字段主要约束
customersid、name、email、createdAt乘客身份
driversid、name、email、dob、location、cabID司机资料与位置引用
cabsid、driverID、type、regNo车辆及司机关联
tripsid、customerID、driverID、status、source、destination、createdAt行程状态与双方归属
ratingscustomerID、driverID、tripID、rating、feedback指定行程的评价
paymentsid、tripID、method、amount、status行程支付结果
  • 关系修正: 原图同时保存 trip.paymentID 与 payment.tripID,实施时应明确唯一权威关联或事务约束,避免两侧指向不同记录;
  • 金额补充: 原图使用 float,实际支付可采用最小货币单位整数或精确十进制,并保存币种;
  • 状态分离: 当前位置高频变化,可保存在有过期规则的索引;行程与支付状态需要可靠持久化;
  • 存储选择: 行程约束适合事务模型,空间查询可使用专用索引,不能要求一个库同时承担全部高频职责;

接口如何防止操作到错误订单?​

requestRide(customerID, source, destination, cabType, paymentMethod, requestID) → ride
cancelRide(customerID, rideID, reason?) → result
acceptRide(driverID, rideID) → result
denyRide(driverID, rideID) → result
startTrip(driverID, tripID) → result
endTrip(driverID, tripID) → result
rateTrip(customerID, tripID, rating, feedback?) → result
  • 原文修正: 取消接口只有 customerID 不足以唯一表达目标,这里补充 rideID;
  • 坐标: source 与 destination 为明确顺序的经纬度对,需要约定坐标系统、单位和合法范围;
  • 身份: customerID 与 driverID 必须对应认证主体,行程操作还需验证关联关系;
  • 幂等: 叫车、取消、接单和结束行程都可能因网络重试而重复,按请求或状态版本保证只产生一次有效转换;
  • 请求与行程: 原文混用 ride/trip,设计时区分“等待匹配的叫车请求”和“已建立的行程”,并明确 ID 映射;

服务怎样分配实时与持久状态责任?​

  • 乘客服务: 管理乘客身份与资料;
  • 司机服务: 管理司机、车辆与上报入口;
  • 叫车服务: 接收请求、聚合附近司机并执行匹配,管理匹配中的状态;
  • 行程服务: 管理已接单后的开始、进行、完成和取消规则;
  • 支付服务: 连接第三方支付并维护支付状态;
  • 通知与分析: 通过事件独立工作,分别负责用户触达与运营指标;
  • 服务边界: HTTP、gRPC、服务发现或网格按通信需要选择,先明确数据所有者再决定部署粒度;

从叫车到完成的主流程是什么?​

乘客提交起终点与车型 → 创建待匹配请求
→ 查询候选司机并估算 ETA → 邀约
→ 某司机原子接单 → 通知双方并持续显示司机位置
→ 接客后开始行程 → 到达后结束行程
→ 支付处理 → 允许评价
  • 状态约束: 每一步只能由允许的前置状态进入,拒绝订单不应意外结束其他司机的行程;
  • 不确定结果: 请求超时后应查询状态而非盲目创建第二个行程;
  • 失败边界: 匹配超时、乘客取消、司机失联与支付失败各有不同处理,不应统一回滚整个现实行程;

司机位置如何持续更新到乘客端?​

  • 上报: 司机端按时间或移动距离上报 GPS 坐标与采样时间,后台运行还要考虑系统权限和省电限制;
  • 连接选择: WebSocket 可双向交换位置与状态,普通周期 HTTP 上报也是可行选项,不能把主动上报本身称为长轮询;
  • 下行: 只有有权查看该司机的乘客接收相应更新,不向所有用户广播完整轨迹;
  • 新鲜度: 每个位置带时间戳并设置过期规则,长期失联的司机不能继续作为可接单候选;
  • 高频优化: 可合并更新并降低静止时采样频率,精度与吞吐按 ETA 和用户体验要求权衡;

经纬度条件查询如何作为附近司机的基线?​

SELECT driver_id, latitude, longitude
FROM driver_locations
WHERE latitude BETWEEN :min_lat AND :max_lat
AND longitude BETWEEN :min_lon AND :max_lon;
  • 初筛: 包围盒找出可能在附近的司机,再用实际地理距离过滤,不把矩形结果当精确半径结果;
  • 单位修正: 原文直接用 X±R 混合经纬度与半径,应先把距离转成合法边界或使用空间查询能力;
  • 性能判断: 无合适索引的全表扫描可能昂贵,但不能因此断言 SQL 本身无法支持大规模空间查询;

Geohash 怎样用于司机候选查询?​

  • 路径: 给司机当前位置计算 Geohash,查询覆盖乘客周围区域的多个编码范围,再按坐标距离过滤;
  • 索引位置: 热点位置数据可放内存索引,以减少每次候选查询的访问成本;
  • 边界条件: 跨网格司机不能因前缀不同而被遗漏,原理及精度表见 空间索引;
  • 使用判断: 实现简单且能复用字符串索引,但需要处理覆盖范围、误差和高频更新;

四叉树怎样组织高密度地区的司机?​

  • 路径: 按区域保存司机,密度超过阈值时继续细分,查询只遍历与搜索范围相交的区域;
  • 位置更新: 司机跨区域时移出旧节点并加入新节点,查询应避免读到不一致的重复位置;
  • 缓冲: 可以先记录最新位置并批量更新索引,以吞吐换取明确可控的新鲜度延迟;
  • Hilbert 映射: 可辅助布局或范围访问,但四叉树本身不依赖该算法,机制见 四叉树;
  • 选择边界: 原文偏好四叉树,但应结合更新频率、区域密度和现有空间数据库能力验证,不能仅凭概念合适认定最优;

多个司机同时接单时怎样避免重复匹配?​

  • 原子状态转换: 只有订单仍待匹配且司机仍可用时,才能同时写入订单归属与司机占用状态;
  • 竞争胜者: 使用条件写、版本号或事务决定唯一成功者,失败者收到明确的已被接单结果;
  • 原文修正: 单进程 Mutex 只保护一个进程内线程,多个服务实例竞争时需要共享权威状态的原子保证;
  • 跨边界: 若订单和司机状态跨存储,需有明确协调、预占租约与补偿策略,相关机制见 分布式事务;
  • 租约过期: 旧接单请求不能在租约已失效后覆盖新归属,应校验版本或隔离令牌;

如何从附近候选中选择合适司机?​

  • 候选过滤: 先排除位置过期、车型不符、不可接单或无权限服务该区域的司机;
  • 排序: 综合到达时间、路网、评分和相关反馈,而不只比较直线距离;
  • 邀约: 可分批优先通知高排名司机,设置响应期限,避免同时向大量司机发出互相冲突的承诺;
  • 高需求: 动态加价可反映供需紧张,但价格必须在订单和支付中具有可解释、稳定的确认版本;

支付完成如何可靠地反映到行程状态?​

  • 第三方支付: 原文采用 Stripe 或 PayPal 等处理器,把支付渠道细节交给专门服务;
  • 完成信号: 不以浏览器跳回成功页作为唯一证据,应验证服务端事件并在必要时主动查询支付结果;
  • Webhook: 校验事件签名,依据事件或支付 ID 幂等更新,允许重复或乱序到达;
  • 状态边界: 行程已完成而支付待处理是合理中间状态,不能因网络超时再次扣费或把真实行程抹去;
  • 参考: Stripe Webhook 文档说明签名验证、重复事件与顺序处理要求;

地理分区与缓存怎样控制热点?​

  • 区域分区: 按城市或业务服务区域归属位置数据,减少大多数附近查询的跨区请求;
  • 边界查询: 用户靠近分区边缘时要查询相邻区域,不能把邮编边界当作物理距离边界;
  • 热点地区: 人群和车辆分布不均时需要细分热门区域,单纯一致性哈希不能同时保证地理邻近与均匀业务负载;
  • 缓存: 保存最近位置并附采样时间和 TTL,LRU 只能管理容量,不能说明位置仍有效;
  • 未命中: 可尝试读取可靠位置源,但过旧位置只能作为历史信息,不能直接用于实时派单;

行程通知怎样避免干扰派单?​

  • 通知: 接单、到达和支付事件进入独立通知流程,平台分发见 即时通信,通知失败不改变已提交的司机归属;

行程统计应该记录到哪里?​

  • 统计: 收集等待时长、取消、匹配成功率和行程事件,原文误用视频案例 views 表,这里应落入行程与业务事件模型;

节点故障时如何保持派单约束?​

  • 冗余: 服务、入口、缓存和持久存储部署适当副本,关键接单判断仍回到权威状态,不能从延迟副本决定可用司机;
  • 降级: 排名或分析故障可采用简化候选策略,但不可牺牲重复接单约束;
  • 验证: 覆盖两人抢一车、两车接一单、取消与接单同时发生、位置过期、跨区查询、重复支付事件和连接中断;