还记得最开始做出行调度那阵子,团队里最常被问到的一句话就是“ax到底调度了什么”。订单来了派给最近的司机,不是理所当然的事吗?等真正钻进去才发现,所谓调度,是一场在时间、空间、人和利益四个维度上同时进行的博弈。司机绕路接了单,乘客觉得等太久;平台想提高完单率,司机关单拒接;高峰期明明是需求量最大的时候,运力反而像蒸发了一样散在城市的各个角落。
我这次想写的就是这个“ax”背后的调度系统——它如何从零搭起来、核心算法怎么落地、上线初期踩过哪些要命的坑。无论你是在做出行产品,还是碰巧在做配送、维修、家政这类依赖实时派单的业务,这套思路都能直接移植过去。ax调度的本质,就是用一套实时决策系统,把“谁该去哪、接谁的单、走哪条路”在一秒钟内算清楚。
1. ax调度解决的核心矛盾:一场时空错配的博弈
先说清楚调度在解决什么问题。城市出行的根本矛盾,不是车不够,而是车和需求在时间和空间上永远对不上。早高峰住宅区一堆人等车,商圈的车却排着长队趴活;晚高峰写字楼叫车排队几十号,车却堵在高架上动弹不得。乘客端感知是“没车”,司机端感知是“没单”,两边同时觉得平台不行,这其实是系统在信息撮合上失效了。
1.1 人和车的匹配,本质上是一个实时最优解问题
如果城市里只有十辆车、十个乘客,那调度很简单,司机按距离抢单就行。可一旦规模放大到几千辆车、几万个实时请求,问题就变成了:每一秒都有新订单进来,每一秒都有车辆在移动,任何一次分配都会影响接下来的五分钟里的供需格局。派单不是给当前订单找一个最近司机,而是给接下来时段内的全部订单做一次整体优化。
这就好比你开一家快递站,每天几千个包裹进来,你不能盯着最新的那个包裹去找最近的快递员,你得把所有包裹、所有快递员当前位置、所有预计送达时间放在一起排布,让整体效率最高,否则下午最忙的时候一定会有人闲着、有人爆单。ax调度的核心逻辑是一样的:它维护的不是“订单池”,而是一个动态的供需权衡系统。
1.2 黑盒模型、规则模型和实时调度的区别
很多人以为调度就是一串if else,什么“订单距司机3公里内就派”,实际上这只适用于起步阶段。ax调度从规则驱动走向模型驱动的过程,大概经历了三个阶段:
| 阶段 | 决策方式 | 典型问题 |
|---|---|---|
| 纯抢单 | 司机手动抢,平台不干预 | 冷热区域严重失衡,郊区永远没车 |
| 规则派单 | 按距离/评分/排队时长打分 | 只优化了局部,高峰期“局部最优导致全局崩溃” |
| 智能调度 | 预测 + 优化 + 动态定价联动 | 需要处理复杂实时数据流,但整体均衡 |
到了第三阶段,系统关注的不再是“这一单给谁”,而是“这个区域内未来十五分钟预计有多少单、多少车、谁顺路、谁去接最划算”。这也是ax调度能成为一个独立模块存在的意义——它是平台的大脑,而不是单纯的派单机器人。
1.3 调度模型的第一课:什么数据才算有效输入
搭建调度系统最先要做的不是写算法,而是把数据口径理清。我们当时踩过的一个典型坑是:订单里的定位坐标精度不够。司机端GPS信号在高架桥下衰减严重,乘客在地铁站里发的定位可能漂移几百米。你把漂移坐标喂进模型,派出来的全是错位订单。
所以ax调度一开始就要建立一套数据清洗管线:对坐标做围栏校正、把连续多点轨迹做道路绑定、剔除明显异常的瞬时速度跳变。真实场景里,百分之八十的调度问题都不是模型不够聪明,而是喂给模型的数据是脏的。这一步做扎实了,后面所有环节才谈得上准确。
2. 从零搭建ax调度引擎:实时计算架构的选型思路
调度系统对实时性的要求极高。一个订单从进来到最后派出去,用户能感知的等待时间也就几秒,超出这个范围体验就很差。所以ax调度的底层架构,必须围绕“事件驱动、低延迟、可降级”三个关键词来搭。
2.1 为什么不能用离线批处理来做调度
有一种直觉方案是把所有订单攒起来,每五分钟做一次全局最优匹配。听起来效率高,但放到真实场景是完全不可用的:高峰期订单随时进、司机随时挪动,五分钟前的司乘位置轰到五分钟后再决策,匹配结果已经失真。用户端看到的是车明明在旁边,等了十分钟还没被派单。
ax调度选择的是滑动窗口式的实时匹配。每个订单进来后在毫秒级完成一次“预匹配”,同时进入一个短窗口池(通常是15~30秒)。窗口期内如果有更合适的运力出现,订单可以被改派;窗口结束时锁定派单,避免司机空等。这既保证了实时性,又给了系统一定的优化空间——本质上是用时间换全局效率。
2.2 派单引擎的数据链路:从接入到落单
一条订单从产生到确认,在ax调度系统里大约经过六个环节:
- 订单接入:乘客端发起请求,进行去重、风控、基础信息校验
- 坐标修正:对乘客和司机位置做反向地理编码,绑定到路网
- 候选集召回:以订单为中心,检索周边可用运力,按半径分层
- 特征计算:拼装司乘距离、预计接驾时长、该司机当前方向、实时路况等特征
- 模型评分:接单概率模型给出每个候选司机的接单可能性
- 派单决策:结合全局调度策略,选择最优司机,下发派单指令
这六个环节必须在几百毫秒内完成。我们当时的服务链条很长,最初直接在业务服务器里串行跑,高峰期一压就超时。后来把特征计算、模型推理拆成独立服务,用消息队列异步衔接,派单引擎只做决策,才把P95耗时压到800毫秒以内。
2.3 事件流处理与状态管理
调度系统的复杂度很大程度来自状态管理——每个司机都有实时状态(空闲、接单中、去接驾、载客中、收工),每个状态变化都会触发后续逻辑。ax调度里有一张“司机状态机”,把司机的全生命周期抽象成状态转换,再配合GPS轨迹流、订单流、支付流三个事件流做驱动。
其中最容易出问题的就是状态不一致。比如司机接单后手机断网,平台侧认为他还是空闲,又给他派了新单;他那边实际上已经在去接上一单的路上了。要避免这种问题,必须给每次派单做“乐观锁+状态校验”:派单前再次确认司机状态,宁可多查一次缓存,也不能让司机同时接到两个派单任务。这个坑我们在上线后第二周就遇到了,后面会细说。
3. 核心算法落地:接单率预估、ETA计算和动态定价
ax调度真正拉开差距的部分,在于算法层。订单和司机之间不是“有没有空”的关系,而是“接了划不划算”的关系。司机也是理性人,位置偏远、去接驾要空跑十几分钟的单子,即使推送过来他也很可能取消。所以系统必须预判司机的行为,不能把司机的响应当成理所当然。
3.1 接单率预估:司机的“感觉”是可以被模型化的
我们训练了一个模型,输入特征大致包括:派单距离、预计接驾时间、订单终点方向与司机回家方向是否一致、当前时段、所在区域的热度、司机历史接单率等。输出是一个0到1的接单概率。低于一定阈值的订单不会硬派,而是通过加价、加调度费的方式调整订单吸引力后再发出。
与其说这是算法,不如说这层逻辑是在模拟一个老司机的兜底直觉。老司机扫一眼订单就知道值不值得接,模型的本质就是把这种经验规律化。具体实现可以采用轻量级GBDT模型,不用上深度模型,训练数据充裕、特征稳定,效果已经足够。用深度模型反而容易在特征漂移时出各种诡异错误。
3.2 ETA计算:不只是距离除以速度
ETA(预计到达时间)是司机和乘客都盯着的指标。ax调度里的ETA不是一个固定值,而是结合实时路况、红绿灯等待、上下车步行时间的综合预测。这里有个反直觉的经验:在拥堵核心区,单纯缩短司机接驾距离对体验的提升并不明显,反而是减少红绿灯路口数更关键。等红灯的时间往往比多跑一公里还长。
所以在路径规划上,我们做了专门的“转弯惩罚系数”:优先推荐左转少、主路优先的路线,而不是距离绝对最短的路线。测下来同一对起终点,这种路线在高峰期平均能压缩10%到15%的接驾时间。这个系数光靠经验调不行,要拿历史轨迹数据反复拟合。
3.3 动态定价不能乱动:价格是调度的第一杠杆
调度的目标之一是让运力流向需求区,最直接的驱动就是价格。ax调度的动态定价不是简单的高峰期涨一倍,而是用“调度补偿金”的方式,把决策落到区域粒度的供需比上。当一个区域供需比低于阈值,系统自动对该区域订单加调度费,并在司机端显示“该区域单量多、有额外奖励”,引导司机向需求区空驶。
这里要特别注意节奏。动态调价如果变动太频繁、幅度太大,用户端很容易产生“平台宰客”的情绪,司机端也会形成“蹲奖励单”的投机行为。我们后来做了一套带阻尼的调价策略:价格变化率限制在每五分钟不超过15%,并且在连续动态调价两个周期后强制平稳15分钟。效果是:用户投诉明显下降,司机空驶率反而降了四个百分点。
4. 上线初期的调度事故:从派单风暴到大面积取消的完整复盘
任何调度系统都要经过真实流量的毒打。我们ax调度上线第二个星期就出了一次严重事故,这里把完整排查链路写出来,比任何技术文档都有参考价值。
4.1 事故现象:同一时间大量司机收到派单又瞬间取消
那个周五晚高峰,某中部城市区域突然之间派单量暴涨,但完单率却断崖式下跌。后台监控显示:每分钟派单指令数是平峰的七倍,但真正被司机接收的比例不到两成。大量司机的App在短短几分钟内连续弹派单——司机点接收后一秒,系统又弹了取消。整个区域陷入派单风暴,乘客那边则是永远等不到车。
一开始团队怀疑是运营活动带来的流量高峰,但请求量数据并不支持这个判断。通过链路追踪发现,问题出在订单状态机与缓存的一致性上。
4.2 根因:缓存里的司机状态和实时状态错位
复盘细节是这样的:派单引擎为了性能,把司机状态放在Redis缓存里,并设置了3分钟过期时间。正常情况下,司机有状态变更时会主动上报,服务端会及时更新缓存。但那天一位司机在隧道里长时间断网,他手机里的状态是“载客中”,而服务端缓存在3分钟过期后自动重置成了“空闲”。
重置之后,调度引擎把这位司机当成新增运力拉进了候选池。因为他车上其实有乘客,系统派发的每一个新单他都不可能接收,于是只能走超时取消流程。一辆车的脏数据并不致命,致命的是同一个区域同时出现了几十辆这种“幽灵车”。候选集里名义运力充足,系统就会降低对真实司机的派单权重,导致真实司机反而接不到单,整个区域供需评估彻底失真。
4.3 修复方案:状态双写校验与候选集隔离
那次事故之后,我们做了三项修复,这三项现在成了调度系统的标配:
- 状态双写校验:司机状态除了缓存外,同时落一份到数据库。派单前必须取缓存,但每五次派单里至少有一次去数据库做状态校验,防止缓存长期失真。
- 候选集隔离:GPS轨迹连续多个周期未更新的司机,直接从候选集排除,不再依赖状态字段判断。把“数据新鲜度”作为候选运力的硬门槛。
- 派单限流熔断:当单个订单的候选司机连续N个都未响应时,暂停向该区域派单三分钟,让系统恢复冷静,而不是用更激进的派单频率去撞运气。
修复上线后,同样的场景又模拟压测了好几轮,再没出现过级联式派单风暴。这里面的教训其实很朴素:任何缓存都是不可靠的,定时过期在分布式系统里是最危险的设计之一,状态类数据必须有多级确认机制。
5. 从“能用”到“好用”:ax调度的进阶优化方向
基础调度稳定之后,ax的价值开始体现在更复杂场景的统筹上:跨区域运力预测、拼车顺路单、以及调度和评价体系的联动。这些方向才是让产品产生长期粘性的部分。
5.1 供需预测:不被动响应,而是提前布防
调度不能每次都等订单攒到一堆才开始派车,而是要在需求还没爆发时就把运力引导过去。我们做了一个基于历史时段、天气、节假日、区域POI分布的供需预测模型,提前15分钟输出各个区域的预计订单量和预计空闲运力。调度引擎拿到预测结果后,会对供需比超过阈值的区域提前发出“驶入奖励”,引导周边两公里范围内的司机慢慢靠拢。这套系统的效果是:高峰期乘客平均等待时长下降了约23%,区域完单率提升了近9个百分点。
没有必要把这个模型做得特别复杂。我们最初用的是一个带时间特征和节假日特征的XGBoost模型,后来加了天气和POI特征。预测精度已经能满足调度引导需求。真正难点是区域的热力变化在跨城、跨商圈时差异极大,需要按城市单独校正阈值。
5.2 顺路拼车和组合优化
调度系统的另一个高级玩法是顺路单匹配。两个订单起点相近、终点方向相近时,系统可以安排车辆同时接下,只在价格上做区分,对司机来说同样的行驶里程完成两单的收入。这种玩法对算法要求更高,因为它要同时优化两条路径的总时长,还要考虑先送谁后送谁的用户体验差异。
我们在ax里加了路径冲突指数作为硬约束:两个订单的终点方向夹角小于30度、共享路径超过总行程70%才允许合并。低于这个比例的拼车,往往会因为绕路太久牺牲完整体验,得不偿失。上线后,拼车类订单的司机接受率和乘客五星率都明显高于普通订单。
5.3 调度与评价体系的联动:不能只盯着效率
最后一个容易被忽视的维度是公平性。如果调度算法永远把好单子派给接单率高的头部司机,尾部司机的收入会越来越差,形成恶性循环。ax调度的策略是,在模型评分中加入一个“机会公平项”——当某个司机连续两次由于距离远被系统跳过派单后,第三次会适当上浮他的优先级。宁可稍微牺牲一点完单效率,也要保证司机群体的收入分布不至于极端分化。
这一点在实际运营中很重要。调度系统的长期稳定,不只是靠算法精度,还要靠司机端有“在这个平台跑有奔头”的感觉。数据上,加入公平项后,司机次日出车率提升了约五个点,这比任何精准派单带来的短期效率提升都更有价值。
最后分享一个我反复和团队强调的观点:调度系统的目标不是把每一单都派得最精准,而是让整个系统在任何天气、任何时段都能保持稳健。你永远无法让每一个司机和乘客都满意,但你可以让系统的供需失衡在几分钟内被感知、被修正、被消化。ax走到今天,真正沉淀下来的不是多少行代码,而是这套实时感知、快速决策、稳健兜底的调度思维。如果你正在搭建类似的产品,建议先把状态一致性、数据新鲜度和候选集质量这三件事做扎实,再回头调算法。基础不稳的时候,再聪明的模型也只是让错误发生得更快而已。