# 网约车 / 出行调度 架构模板 > **代表产品**:Uber、滴滴、Lyft、Grab > **一句话定位**:实时掌握海量司机和乘客的位置,在几秒内把最合适的车和人匹配上,并全程跟踪行程。 --- ## 1. 一句话定位 网约车系统 = **一张不断变化的「活地图」** + **一个秒级撮合供需的「实时匹配引擎」**。 它和普通系统最不一样的地方:**核心数据是「位置」,而位置每几秒就变一次、几秒后就过时。** 整套架构都在回答两个问题:「**我附近现在有谁?**」和「**这个人和那辆车,怎么尽快配上?**」——这需要地理空间索引、海量实时位置流、和一个撮合引擎协同。 ## 2. 业务本质:它在解决什么问题 它是一个**双边市场**:一边是有空闲运力的司机,一边是有出行需求的乘客。系统解决的是「**在时空上,把闲置的车和等车的人高效匹配起来**」,顺便解决信任(评价)、定价、安全。 钱从哪来:每单抽佣、动态溢价、订阅 / 会员、广告。匹配效率越高(空驶越少、等待越短),双边体验和平台收入越好。 ## 3. 核心需求与约束 **功能性需求:** - [ ] 司机 / 乘客实时定位与上报 - [ ] 叫车 → 就近匹配派单 - [ ] 路径规划与到达时间(ETA)预估 - [ ] 动态定价(高峰溢价) - [ ] 行程跟踪、支付、评价 **非功能性需求 / 质量属性:** | 质量属性 | 目标 | 为什么对这类系统重要 | |---|---|---| | **匹配延迟** | 秒级 | 等太久乘客就取消、司机就跑单 | | **位置写入吞吐** | 极高 | 每个司机每几秒上报一次 = 海量写 | | **地理查询速度** | 毫秒级 | 「附近的车」要快速算出来 | | **高峰扩展性** | 弹性 | 早晚高峰、雨天、散场,流量剧烈波动 | **关键约束(不可逾越的边界):** - 🔴 **位置数据高频、海量、易失**:写入量巨大,但每条只活几秒,旧的没价值。 - 🔴 **地理查询是「特殊查询」**:「方圆 3 公里内的车」既不是主键点查、也不是范围扫描,而是**二维空间查询**,普通索引干不了。 - 🔴 **强地域性**:北京的供需和纽约毫不相干——这是个**天然的分片维度**。 - 🔴 **供需实时剧烈波动**:演唱会散场、暴雨,瞬间需求爆炸。 ## 4. 架构全景图 ``` 司机 App 乘客 App (每几秒报位置) (叫车) │ │ ▼ ▼ ┌───────────────┐ ┌──────────────────┐ │ 位置接入服务 │ │ 叫车请求 │ │ (超高频写, │ └─────────┬────────┘ │ 主要落内存) │ ▼ └──────┬────────┘ ┌──────────────────────────┐ ▼ │ 匹配引擎 │ ┌───────────────┐ │ ① 用地理索引查「附近的车」 │ │ 地理空间索引 │◀──│ ② 按距离/ETA/评分挑最优 │ │ GeoHash/网格 │ │ ③ 派单、等司机接受 │ │ (按区域分片) │ └────────────┬─────────────┘ └───────────────┘ ▼ ┌──────────────────────────────────────┐ │ 行程服务(状态机)→ 定价 → 支付 → 评价 │ │ 全程位置跟踪、ETA 更新、推送 │ └──────────────────────────────────────┘ (轨迹异步写入时序存储,用于回放/分析) ``` > 灵魂部件是**地理空间索引 + 匹配引擎**:前者让「查附近」从「不可能」变成「毫秒」,后者在这张活地图上完成撮合。其余都是支撑这次撮合与之后的行程。 ## 5. 组件职责 - **位置接入服务**:承接司机 / 乘客超高频的位置上报。*为什么需要*:写入量极大,且大部分位置只需放内存、无需逐条持久化(见决策 2)。 - **地理空间索引**:把二维经纬度编码成可快速检索的结构(GeoHash / 网格 / 四叉树),支持「附近的车」查询。*为什么需要*:这是网约车区别于一切普通系统的核心能力。 - **匹配引擎**:查附近候选 → 按距离 / ETA / 评分挑选 → 派单。*为什么需要*:撮合是平台创造价值的核心动作。 - **行程服务(状态机)**:管理一单从「已派单 → 司机在路上 → 行程中 → 完成」的状态。*为什么需要*:行程是有严格状态流转的,且关乎计费。 - **动态定价**:用实时供需比调价。*为什么需要*:高峰时用价格杠杆平衡供需、削峰(见决策 4)。 - **推送 / 长连接**:实时把派单、位置、ETA 推给两端。*为什么需要*:出行是强实时交互。 ## 6. 关键数据流 **场景一:司机持续上报位置(超高频写)** ``` 1. 司机 App 每 4 秒上报一次 {司机ID, 经纬度} ──▶ 位置接入服务 2. 更新地理空间索引里该司机的位置(主要在内存) 3. (旁路)按采样把轨迹异步写入时序存储,用于回放、结算、分析 ── 注意:不是每条位置都进数据库,那样会写爆且无意义 ``` **场景二:乘客叫车并匹配(系统的核心动作)** ``` 1. 乘客叫车 {上车点经纬度} ──▶ 匹配引擎 2. 用地理索引查「上车点附近的空闲司机」── 毫秒级拿到候选 3. 按 实际道路 ETA、司机评分、是否顺路 排序,挑最优 4. 派单给该司机 ──▶ 推送,等其接受(超时则派给下一个) 5. 接受后 ──▶ 行程服务建单,进入「司机在路上」状态,两端实时看位置 ``` ## 7. 数据模型与存储选择 核心实体:`司机位置(高频更新、易失)`;`行程(状态机)`;`用户 / 司机资料`;`轨迹(时序)`。 | 数据 | 存储类型 | 为什么 | |---|---|---| | 实时司机位置 | 内存级地理索引 | 超高频更新、查询要快、旧数据可丢 | | 行程订单 | 关系型 | 状态流转 + 计费,要事务、强一致 | | 历史轨迹 | 时序 / 对象存储 | 海量追加、按时间回放、用于分析结算 | | 用户 / 司机资料 | 关系型 | 结构化、要一致 | > 教学点:**司机位置是典型的「易失高频数据」**——它的价值只在「此刻」,几秒后就被新值覆盖。把它逐条强一致落库,是把宝贵的写入能力浪费在不需要持久化的数据上。 ## 8. 关键架构决策与权衡 ⭐ **决策 1:「附近的车」怎么查?(网约车的立身之本)⭐** - 数据库 `WHERE` 算距离:对每辆车算一遍到上车点的距离再筛——全表扫描,车一多就崩。 - 空间索引(GeoHash / 网格):把二维位置编码成可索引的结构,「附近」变成「查相邻的几个格子」。 - **取向**:必用空间索引。**它把『二维邻近』这个特殊查询,变成了普通的、可索引的查找。** **决策 2:位置数据,逐条落库还是主要放内存?⭐** - 逐条持久化:每个司机每几秒一条,海量写入直接压垮数据库,而且这些数据几秒后就过时。 - 主要放内存(地理索引)+ 采样持久化轨迹:实时位置在内存里更新,只按需采样保存轨迹。 - **取向**:实时位置放内存,轨迹采样异步落库。**别为「不需要持久化的易失数据」付出持久化的代价。** **决策 3:就近匹配,还是批量全局最优?** - 就近贪心:来一单立刻派最近的车,简单、快。 - 批量撮合:攒一小批请求和车一起算全局最优(减少总空驶)。 - **取向**:起步就近;规模大后引入批量撮合提升整体效率,代价是单次匹配要多等一两秒。 **决策 4:高峰怎么扛?——用定价当「经济限流器」** - 硬扛:供给不变、需求暴涨,大量人打不到车、体验崩。 - 动态溢价:涨价**抑制部分需求、吸引更多司机上线**,自动让供需重新平衡。 - **取向**:动态定价本质是**用经济杠杆做流量调节**,是限流 / 削峰的高级形态。代价是用户体感差、需谨慎设计与合规。 **决策 5:按地域分片。** - 强地域性让分片变得无比自然:每个城市 / 区域是几乎独立的系统,跨区域无交互。**这是「顺着业务的天然边界切」的范例**,水平扩展极干净。 ## 9. 规模化与瓶颈 - **第一个瓶颈:位置写入洪峰。** → 破解:内存地理索引 + 按区域分片,写入压力天然被地域打散。 - **第二个瓶颈:局部供需爆炸**(机场 / 演唱会散场)。→ 破解:区域隔离不影响别处 + 排队 + 动态定价削峰。 - **第三个瓶颈:海量长连接**(每个在线用户一条)。→ 破解:长连接接入层水平扩、按区域就近接入。详见 [实时通讯模板](../realtime-chat/README.md)。 - **跨区域几乎独立**:地理分片让全球扩展近似「复制一套到新区域」,这是它最舒服的地方。 ## 10. 安全与合规要点 - 🔴 **位置隐私**:实时位置是极敏感数据,要严格授权、最小化留存、传输加密;行程结束后弱化精度。 - **人身安全**:行程录音、一键报警、轨迹可追溯、紧急联系人,是这类产品的刚需而非附加。 - **欺诈**:刷单、虚假定位(GPS 作弊)、司乘合谋骗补——需要轨迹一致性校验与风控。 - **支付安全**:见 [支付系统模板](../payment-system/README.md)。 ## 11. 常见误区 / 反模式 - ❌ **用数据库 `WHERE` 算距离做「附近查询」** → ✅ 空间索引(GeoHash / 网格)。 - ❌ **每条位置都强一致落库** → ✅ 实时位置放内存,轨迹采样异步持久化。 - ❌ **全局一个大池子撮合,无视地域** → ✅ 按区域分片,顺着天然边界切。 - ❌ **高峰只想着加机器硬扛** → ✅ 排队 + 动态定价,用经济杠杆削峰。 - ❌ **把位置当普通业务数据强一致处理** → ✅ 认清它「易失、高频、只活几秒」的本质。 ## 12. 演进路线:MVP → 成长期 → 成熟期 | 阶段 | 规模量级 | 架构长什么样 | 此时该操心什么 | |---|---|---|---| | **MVP** | 单城市 | 内存地理索引 + 就近贪心匹配 + 一个行程库 | 先把「叫车 - 派单 - 完成」跑通 | | **成长期** | 多城市 | 按地域分片、动态定价、ETA 优化、长连接接入层 | 匹配效率、高峰扩展、位置写入 | | **成熟期** | 全球多区域 | 批量全局撮合、机器学习 ETA / 定价、拼车、运力调度预测 | 全局效率、容灾、安全合规、成本 | ## 13. 可复用要点 - 💡 **特殊的查询,需要专门的索引。** 地理邻近用空间索引,正如全文检索用倒排索引——把特殊问题映射到能被索引的结构上。 - 💡 **易失的高频数据,不必持久化每一条。** 先问「这数据多久就没用了」,再决定要不要、以及怎么存。 - 💡 **用经济杠杆(定价)调节流量,是限流 / 削峰的高级形态。** 价格能同时压需求、拉供给,比单纯排队更聪明。 - 💡 **顺着业务的天然边界(地域)分片,扩展最干净。** 跨片无交互时,水平扩展几乎等于「复制一套」。 ## 🎯 随堂检验 --- ## 参考原型与延伸阅读 > 本模板基于以下**真实开源项目**与**工程剖析**整理。 **🔧 开源原型(可直接读代码):** - [uber/h3](https://github.com/uber/h3) — Uber 开源的六边形分层地理空间索引(C 核心 + 多语言绑定),网约车定价与派单的空间索引基础。 **📖 工程博客:** - [H3: Uber's Hexagonal Hierarchical Spatial Index (Uber)](https://www.uber.com/us/en/blog/h3/) — 为何用六边形网格优化定价与调度。 - [How Uber Scales Their Real-time Market Platform (High Scalability)](http://highscalability.com/blog/2015/9/14/how-uber-scales-their-real-time-market-platform.html) — DISCO 派单系统:按地理分区匹配供需、全局分配优化。 --- > 📌 一句话记住网约车:**它不是「一个叫车 App」,而是「一张每秒都在变的活地图 + 一个在地图上秒级撮合供需的引擎」——所有设计都在回答『怎么又快又准地知道附近有谁、并把人和车配上』。**