机器人租赁平台开发做到中期,客户提得最多的一个问题就是:"能不能像滴滴一样,订单进来自动派给最合适的服务商?"这个直觉是对的,但照抄网约车调度一定会翻车。我们团队在做调度系统那两个月里,反复推演过两种业务形态的差异,这篇文章把我们的抽象过程和评分函数、兜底机制的设计思路完整讲一遍。
一、为什么不能照抄滴滴:两种调度形态的本质差异

先说结论:网约车调度是"分钟级、人在路上"的实时匹配,机器人租赁调度是"天级、设备在固定网点"的预约型匹配。看起来都是"供需撮合",但底层逻辑差异很大。
| 维度 | 网约车调度 | 机器人租赁调度 |
|---|---|---|
| 资源位置 | 动态,在路上移动 | 静态,固定在服务网点/仓库 |
| 履约周期 | 分钟级(一趟行程几十分钟) | 天级到月级(短则 1 天,长则 12 个月) |
| 供需并发 | 实时抢单,秒级响应 | 预约制,订单提前数天甚至数月 |
| 资源状态 | 完成即释放 | 占用整个租期,档期互斥 |
| 调度约束 | 距离 + 接单意愿 | 档期空闲 + 场景匹配 + 交付半径 |
| 失败代价 | 派单失败重新排队 | 违约成本高(展会日期是硬约束) |
其中最要命的是两条:
- 档期是区间占用。滴滴司机送完一单立刻可接下一单,而一台迎宾机器人被某展会租走 3 天,这 3 天内它对所有其他订单"不存在"。调度的输入不是"设备空闲与否"的布尔值,而是一段区间集合。
- 履约日期能提前但很难推迟。网约车晚接你 3 分钟感知不明显,但展会 10 号开幕,机器人 11 号才到,这单就是事故。所以调度系统必须把"履约确定性"排在"最优性"前面。
想清楚这两点,我们的调度系统就不是"派单引擎",而更像一个带约束的预约撮合系统。
二、供需匹配模型:设备池、订单池、服务网点三要素

抽象之后,我们的调度问题只剩三个实体和它们之间的关系:
服务网点 (ServicePoint)
├── 拥有 设备池 (DevicePool):若干台可租设备,各有档期与状态
└── 覆盖 交付半径:可承接哪些区域的订单
订单池 (OrderPool):待履约订单,带场景标签、期望档期、交付地址
核心表结构我们是这样落的:
sql
CREATE TABLE t_service_point (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
city_code VARCHAR(12) NOT NULL,
lng DECIMAL(10,6) NOT NULL COMMENT '经度',
lat DECIMAL(10,6) NOT NULL COMMENT '纬度',
service_radius_km INT DEFAULT 50 COMMENT '交付半径',
capacity_score DECIMAL(4,2) COMMENT '履约能力分',
status VARCHAR(20) DEFAULT 'ACTIVE'
);
CREATE TABLE t_device (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sp_id BIGINT NOT NULL COMMENT '所属网点',
model_id BIGINT NOT NULL COMMENT '机型',
device_no VARCHAR(64) NOT NULL,
status VARCHAR(20) NOT NULL COMMENT 'IDLE/RENTED/MAINTAIN/DISABLED',
KEY idx_sp_model (sp_id, model_id, status)
);
CREATE TABLE t_dispatch_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
model_id BIGINT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
deliver_lng DECIMAL(10,6),
deliver_lat DECIMAL(10,6),
scene_tag VARCHAR(32) COMMENT '场景标签:展会/迎宾/巡检',
dispatch_status VARCHAR(20) DEFAULT 'PENDING',
assigned_device_id BIGINT COMMENT '命中设备',
version INT DEFAULT 0
);
调度的本质,就是给订单池里的每条 PENDING 记录,在设备池中找到一个"档期无冲突 + 场景匹配 + 交付成本可接受"的设备。 注意我们是"订单找设备",而不是"设备找订单"------因为租赁平台大多数时间设备供给是充裕的,瓶颈在订单履约确定性上。
三要素之间的关系链也值得画清楚:订单不直接找设备,而是先按"机型 + 期望档期 + 交付地址"匹配到一组候选网点,再在网点内的设备池里挑具体设备。两段式匹配的好处是,当某网点没有可用设备时,系统可以自然地把订单流转给相邻网点,而不是报一句"无可用设备"把客户挡在门外。我们在业务分析阶段发现,将近三成的"无货"其实是"本网点无货、邻网点有货",两段式匹配直接把这批订单救了回来。
网点维度还有一个容易被忽视的设计点:库存分布要带"能力属性"。同样是迎宾机器人,有的网点有配套的技术驻场人员,有的网点只能做纯设备交付;有的网点有充电仓储条件可以承接长租,有的只适合短租快转。这些属性会影响调度结果------一个需要驻场技术支持的大型展会订单,即使邻网点的距离更近,也可能应该派给稍远但有驻场能力的网点。我们把网点能力建模成结构化标签,与订单要求做匹配过滤,这在后续上线驻场服务品类时省掉了一次架构调整。
另外有一个我们踩过的坑值得记录:设备池一定要区分"平台直营设备"和"商家入驻设备"两个来源。商家的设备有自主档期编排权,调度引擎只能"建议分配",不能像直营池那样"强制占用"。这个区别决定了后面评分函数里要有商家维度的因子。
三、就近调度评分函数:三个因子的权衡
调度候选集的生成逻辑是先粗筛后精排:
sql
-- 第一步:粗筛候选设备(机型匹配 + 档期无冲突 + 状态可用)
SELECT d.id, d.sp_id
FROM t_device d
JOIN t_schedule s ON s.device_id = d.id
AND s.start_date < #{endDate} AND s.end_date > #{startDate}
WHERE d.model_id = #{modelId}
AND d.status = 'IDLE'
AND s.id IS NULL
AND d.sp_id IN (
SELECT id FROM t_service_point
WHERE ST_Distance_Sphere(POINT(lng, lat), POINT(#{lng}, #{lat})) / 1000
<= service_radius_km
)
LIMIT 50;
粗筛出的候选设备再进入精排打分。我们的评分函数是三因子加权和:
Score(device) = W1 × 距离分 + W2 × 档期匹配度分 + W3 × 商家服务分
三个因子分别归一化到 0~100 再加权,权重通过运营后台可配置:
| 因子 | 含义 | 初始权重 | 说明 |
|---|---|---|---|
| 距离分 | 设备网点到交付地址的球面距离映射 | 0.40 | 50km 内线性衰减,超过按 0 计 |
| 档期匹配度 | 期望档期与设备空闲区间的贴合程度 | 0.35 | 完全覆盖 100 分;需要转场 buffer 的按扣分 |
| 商家服务分 | 网点历史履约质量 | 0.25 | 逾期率、损坏率、投诉率综合计算 |
核心打分代码大致是:
java
public double score(DeviceCandidate c, DispatchOrder order) {
double distScore = distanceScore(c.getDistanceKm()); // 50km 线性衰减
double scheduleScore = scheduleScore(c, order); // 档期贴合度
double serviceScore = c.getMerchantScore(); // 已离线算好
// 档期匹配度低于 60 直接一票否决:交付确定性优先
if (scheduleScore < 60) return -1;
return weights.getDistance() * distScore
+ weights.getSchedule() * scheduleScore
+ weights.getService() * serviceScore;
}
有几个设计决策值得展开:
1. 档期匹配度拥有一票否决权。 一台距离 2km 但档期要"掐点"的设备,不如一台距离 15km 但前后都有富余 buffer 的设备。我们的档期分算法是:交付日之前是否有维护窗口、归还之后是否有转场 buffer、冲突的预占单是否处于 15 分钟未支付可释放状态,逐项扣分。
2. 商家服务分离线算,不要在线算。 每天凌晨跑一次批任务,把每个网点的逾期率、设备损坏率、工单响应时效折算成 0~100 的分数落库。调度时直接读数,避免在线聚合拖垮响应时间。
3. 权重必须可配置 + 可灰度。 我们上线初期距离权重给到 0.5,结果发现部分展会客户投诉"设备状态差",运营把服务分权重提到 0.3 后投诉率才降下来。权重是业务策略,不是技术参数,必须交给运营。
评分最高的设备进入"预占确认"流程------这里直接复用我们档期系统已有的预占-确认两阶段机制,调度引擎不重复造轮子。
还要交代一下整个调度链路的耗时预算:粗筛 SQL 控制在 50ms 以内(地理半径过滤走索引 + 经纬度粗筛,不在线上做精确球面计算),精排打分是纯内存计算几乎不耗时,整体调度决策 P99 控制在 200ms 内。调度不是秒杀级的并发场景,订单量再大也是"每秒几笔"的量级,不要为了调度性能上缓存和预计算,先把索引和 SQL 写对------这是我们压测后的一致结论。真正需要异步化的是调度之后的履约编排(通知网点、生成配送任务),这些走消息队列解耦即可。
四、调度补偿与人工兜底:机器解决不了的 20%
自动化调度再完善,也必然有一批订单机器处理不了。我们统计上线后前三个月的数据,大约 12%~18% 的订单最终靠兜底流程消化。所以兜底不是异常处理,是系统的正式组成部分。
兜底分两层:
第一层:自动补偿任务。 每 10 分钟扫描一次 PENDING 超过 30 分钟的调度单,按降级策略重试:
java
// 降级链:候选设备逐步放宽
List<RelaxStrategy> fallbackChain = List.of(
new RelaxByScene(), // 放宽场景标签,用"同能力机型"替代
new RelaxByRadius(), // 交付半径 50km → 100km → 200km
new RelaxByDate(), // 档期允许 ±1 天浮动(需客户确认)
);
放宽策略触发时,调度单会打上标记,给客户的报价和确认流程也要跟着变(比如跨城要加运费),所以这一步必须生成"变更确认单"而不是静默改单。
第二层:人工调度工作台。 对自动补偿三轮都失败的订单,转入人工池。工作台要做的是把决策需要的信息一次性给到运营:候选设备列表及其分数明细、冲突原因、客户履约紧急度、历史沟通记录。我们的经验是人工兜底的效率取决于信息呈现,而不是操作自由度------运营最需要的是"为什么派不出来",其次是"系统建议我怎么办"。
最后是一套双向反馈机制:人工改派的结果要回流到评分模型里(人工否决了系统推荐,说明评分函数在某些场景失真),每周复盘一次否决日志,据此调整权重和粗筛规则。调度系统不是上线即结束,它是被这批"人工纠偏数据"持续喂出来的。
度量调度质量,我们看了三个指标:自动调度成功率 (无需人工介入的比例,上线后从 82% 爬到 89%)、履约准时率 (设备在承诺时间前到达现场的比例,稳定在 98% 以上)、调度平均耗时(订单创建到设备分配完成的时长,中位数 4 分钟,主要耗时在商家确认环节)。这三个指标拼起来才完整:只看成功率会误判(把该转人工的硬留在自动链路上),只看准时率会忽略成本,只看耗时又可能牺牲准确性。
五、最佳实践清单
- 先做业务形态的差异化分析,再决定调度模型------预约型撮合和实时派单是两套系统;
- 评分函数三因子起步(距离/档期/服务分),权重交给运营配置,支持灰度;
- 档期匹配度设一票否决线,履约确定性永远优先于最优性;
- 商家服务分离线批算,调度链路只读不写;
- 自动补偿按"放宽策略链"执行,放宽必出变更确认单;
- 人工兜底工作台以"解释为什么"为核心,人工否决日志要回流优化模型。
我们在设备租赁与资产管理类系统的定制开发上有多年落地经验,撮合调度、档期预占、服务网点体系这些模块都经过真实业务验证。如果你也在规划机器人租赁或类似平台,欢迎交流。