· 王思腾 · 系统架构 · 9 分钟阅读

外卖调度系统架构设计:分钟级履约背后的实时调度引擎

外卖履约要求在数十分钟内完成“接单—出餐—配送”。调度系统需要实时感知骑手位置、商家出餐与订单状态,在毫秒间完成亿级组合的最优匹配。本文讲清 LBS、订单流转、智能派单与运力调度是怎么设计的。

外卖履约要求在数十分钟内完成“接单—出餐—配送”。调度系统需要实时感知骑手位置、商家出餐与订单状态,在毫秒间完成亿级组合的最优匹配。本文讲清 LBS、订单流转、智能派单与运力调度是怎么设计的。

外卖是典型的 O2O 实时履约系统:用户下单后,系统要在极短时间内完成商家接单、骑手派单、路径规划,并在地图上实时回传位置。它同时考验高并发写入、地理计算和调度算法工程化能力。

一、业务全景与角色

一次外卖履约涉及四方协同:

  • 用户端:下单、支付、查看进度、评价。
  • 商家端:接单、备餐、叫号出餐。
  • 骑手端:听单、取餐、送达,持续上报 GPS。
  • 调度平台:撮合订单与骑手,做全局最优分配。

核心履约链路:

用户下单 → 商家接单/出餐 → 系统派单 → 骑手到店取餐 → 配送送达 → 结算

每一步都有严格的时间承诺(ETA),任何一环超时都会影响体验和成本。

二、系统架构分层

flowchart TB
    subgraph Access["接入层"]
        U["用户 App"] --- M["商家 App"] --- R["骑手 App"] --- GW["API 网关"]
    end
    subgraph Trade["交易层"]
        T1["订单"] --- T2["支付"] --- T3["营销"] --- T4["评价"]
    end
    subgraph Dispatch["调度层"]
        D1["订单池"] --- D2["运力池"] --- D3["派单引擎"] --- D4["ETA 预估"]
    end
    subgraph LBS["LBS 层"]
        L1["地理围栏"] --- L2["路径规划"] --- L3["实时位置"] --- L4["Geo 存储"]
    end
    subgraph Data["数据层"]
        E1[("Redis")] --- E2["MQ"] --- E3[("时序库")] --- E4[("MySQL")] --- E5["Flink 流处理"]
    end
    Access --> Trade --> Dispatch --> LBS --> Data

调度层是大脑,LBS 层是感知,交易层负责订单生命周期,三者通过消息与状态机解耦。

三、LBS 与实时位置

骑手每几秒上报一次经纬度,高峰期每秒数百万上报,需要专门的存储与计算:

  • 地理位置存储:用 Redis GEO(GeoHash)或基于 GeoHash / H3 的分片索引,快速查询“某商家附近 N 公里的空闲骑手”。
  • 位置上报链路:骑手端 → 网关 → Kafka → Flink 实时清洗/纠偏(地图匹配、去除漂移点)→ 写入在线缓存与时序库。
  • 地理围栏:判断骑手是否“已到店”“已送达”,用围栏触发状态自动流转。
  • 路径规划:调用地图服务算骑行 ETA,并结合历史数据做时间预估修正。

位置数据写多读少、时效性极强,冷热分离:实时位置放内存/Redis,历史轨迹落时序库或对象存储。

四、订单状态机

订单和骑手任务都用状态机驱动,典型订单状态:

stateDiagram-v2
    [*] --> 待支付
    待支付 --> 待接单: 支付成功
    待接单 --> 备餐中: 商家接单
    备餐中 --> 待取餐: 出餐
    待取餐 --> 配送中: 骑手取餐
    配送中 --> 已送达: 送达
    已送达 --> 已完成: 确认/评价
    待支付 --> 已取消: 超时/取消
    已送达 --> 售后中: 申请售后
    已完成 --> [*]
    已取消 --> [*]

骑手任务状态:待抢单/待派单 → 已接单 → 到店 → 取餐 → 配送中 → 已送达。

状态变更通过 MQ 广播,用户端、商家端、调度系统、结算系统各自订阅,保证最终一致。

五、智能派单:系统的核心

派单本质是一个实时匹配优化问题:在满足约束的前提下,把订单分给最合适的骑手,使整体准时率最高、空驶率最低。

1. 派单模式

  • 抢单模式:订单发到骑手池,骑手自主抢。实现简单,但优质单被抢、偏远单无人接。
  • 系统派单(调度):平台全局分配,体验和效率更优,是主流做法。
  • 混合模式:系统派单为主,特定场景开放抢单。

2. 调度算法思路

派单要考虑的因素很多:骑手当前位置与负载、商家出餐时间、用户距离、顺路程度、承诺送达时间、天气与商圈拥堵。

常见工程化做法:

  1. 候选召回:用 Geo 索引快速圈出商家附近的可用骑手,缩小求解规模。
  2. 特征打分 / 成本预估:为每个“订单—骑手”组合估算成本(超时风险、骑行距离、顺路度)。
  3. 优化求解:
    • 简单场景用贪心 + 规则(就近、就闲、顺路);
    • 复杂场景用二分图匹配(KM 算法)或整数规划,批量求全局最优;
    • 并单(一个骑手取多单)可建模为带容量约束的路径问题(VRP 变体)。
  4. 滚动调度:每隔几秒滚动重算,新订单进入、在途订单动态调整。

3. ETA 预估

准时率的关键是 ETA 准确。通常用机器学习模型(GBDT/深度网络)预测:

  • 出餐时长(基于商家历史、当前订单量、菜品);
  • 配送时长(基于距离、路况、天气、骑手经验)。

ETA 既用于用户展示,也作为派单成本函数的核心输入。

六、高并发与稳定性

  • 削峰:午晚高峰是天然流量洪峰,下单、上报全走 MQ 异步化。
  • 弹性扩容:调度服务无状态化,按 CPU/队列积压自动扩缩容。
  • 降级策略:复杂算法超时则回退到简单规则派单;地图服务异常用直线距离兜底。
  • 灰度与仿真:派单策略上线前用历史数据离线仿真,评估准时率和成本,再小流量灰度。
  • 可观测:每一单的派单理由、ETA、轨迹全链路可回放,便于复盘和投诉处理。

七、技术选型参考

场景选型
位置上报/流处理Kafka + Flink
地理存储Redis GEO / GeoHash、H3
在线缓存Redis Cluster
调度求解规则引擎 + 图匹配/OR 优化器
ETA 模型GBDT / 深度网络 + 特征平台
轨迹存储时序库 / HBase / 对象存储

八、总结

外卖调度系统的难点不在单个技术点,而在于把实时数据、地理计算和优化算法缝合成一个稳定的在线系统:

  1. 用 LBS + 流计算实时感知“订单在哪、骑手在哪”;
  2. 用状态机 + 消息驱动订单与运力协同;
  3. 用“召回—预估—优化—滚动”的调度框架做全局最优派单;
  4. 用异步化、弹性扩容和降级兜底保证高峰稳定。

最终衡量调度系统好坏的指标很朴素:准时率、空驶率、骑手收入与用户体验——算法要服务于这些真实业务目标。

本文由 AI 辅助创作,所有内容仅供交流与参考。
分享:
返回博客
从 0 到 100 万用户:架构演进与系统扩展性设计

从 0 到 100 万用户:架构演进与系统扩展性设计

没有一步到位的架构,只有随规模不断演化的架构。本文按用户量级(单机 → 垂直扩展 → 水平扩展 → 服务化 → 多机房)梳理系统在不同阶段遇到的瓶颈与应对,讲清扩展性的两个方向(垂直/水平)、无状态化、分库分表、缓存与读写分离,以及“什么时候该做什么”的取舍。

电商系统架构设计:从下单到履约的全链路

电商系统架构设计:从下单到履约的全链路

电商系统的复杂性藏在库存、订单、支付与履约的每一次协同里。本文从业务域划分出发,带你走一遍下单主链路,并重点讨论超卖、幂等、分布式事务与大促高并发的应对方案。

消息队列原理与选型:解耦、削峰与异步化

消息队列原理与选型:解耦、削峰与异步化

消息队列是分布式系统的“减震器”:异步解耦、流量削峰、事件驱动。本文讲清 MQ 的三大作用、投递语义(至多/至少/恰好一次)、消费幂等、顺序消息、延迟/死信队列,以及 Kafka、RocketMQ、RabbitMQ 的适用场景与常见坑。

缓存设计实战:穿透、击穿、雪崩与一致性

缓存设计实战:穿透、击穿、雪崩与一致性

缓存是高并发系统的第一道防线,但用不好会引入数据不一致、缓存穿透、击穿、雪崩等问题。本文系统梳理缓存读写策略(Cache Aside/Read/Write Through)、三大经典问题的成因与解法、热点 key、缓存与数据库一致性,以及 Redis 在生产中的实践要点。