连锁餐饮外卖配送调度系统的架构设计

0 阅读11分钟

连锁餐饮做外卖,要考虑的一个问题是,哪个平台的骑手可以帮忙送。美团、饿了么这类平台自带运力,用它们的单子就用它们的骑手。但自营渠道(自研小程序、APP)的外卖单就没有现成的骑手,自研的系统得自己对接第三方配送平台。

几十家店还好说,配置一个平台应付过去。上千家门店分布在十几个城市,问题就来了:顺丰在深圳覆盖好,但去了广州就不一定;达达在部分城市有运力,京东众包在另一些城市可能更好。

另外还有一个很核心的问题:价格。不是每次城市都需要用那么贵的配送方的。

因此不太可能在所有城市都只用一家。

这篇讲的就是这个调度系统怎么做。核心是三件事:怎么判断某个地址能不能配送、用哪个平台配送、配送过程中的状态怎么跟踪。


整体链路

先看全貌。

整体链路总览图.png

用户在自营渠道下外卖单后,订单服务记录订单并标记为外卖单。等门店出餐完成,店员在门店端(POS或门店助手)点击叫号,这一步触发配送派单流程。配送调度服务做三件事:验证这个地址有没有运力覆盖、选择哪个配送平台、把订单推给平台。骑手抢单后,各配送平台通过回调接口把状态同步回来,最终反馈给用户。

把这个拆开,核心是三个模块:运力校验、平台路由、状态同步。每个模块都有自己的设计重点。


运力校验

门店对某个用户地址能不能配送,不是一个问题,是一组问题。按照判断成本从低到高排列:门店今天有没有营业、外卖有没有开启、地址到门店的直线距离超没超过配置的最大配送范围、实际骑行距离是否可接受、第三方平台在这个范围内有没有骑手。

这五个条件,前三个直接读库就能判断,后两个要调外部接口(地图API和配送平台API),耗时不可控。

设计上用过滤链来处理这五个判断,同步过滤链串行执行前三步,异步过滤链并行执行后两步,两个异步任务同时发出,等结果汇总后返回。

运力校验过滤链图.png 这样设计的好处是,耗时长的两步不会互相等待,整体延迟取决于最慢的那个,而不是两者之和。用户选地址时触发的运力校验接口,超时阈值设在3秒,能覆盖绝大多数正常情况。

距离计算有个细节值得说一下。直线距离是门店配送范围的粗过滤,单位是米,超过门店配置的最大配送距离就直接拒绝,不用再调地图API了。但直线距离合格不代表骑行距离也合格,因为地形、道路弯绕,甚至中间隔着一座桥等,都会让实际骑行距离比直线大很多。这两步都得做,顺序不能反。

另外,不同配送平台的配送半径可以独立配置一个系数。比如同一个门店,顺丰的配送系数是1.0,某个平台的系数可以配0.9,意思是同样的骑行距离,判断这个平台是否覆盖时要乘以0.9。这给运营留了调控空间,不用改代码。


平台路由与调度策略

运力校验通过之后,具体用哪个平台派单,是这个系统最核心的设计决策。

配置维度

路由规则需要支持两个维度的配置:城市维度和门店维度。

城市维度控制默认策略:深圳默认用顺丰,广州默认用美团,成都默认用京东。这层配置由运营在后台维护,不用改代码。大部分门店跟着城市策略走就够了。

门店维度是补充:某家门店有特殊需求,或者某个平台在这家门店附近表现不好,可以单独覆盖城市默认策略。门店维度的优先级高于城市维度。

配置数据结构上,门店表维护各平台的支持标志和优先级列表,同时挂一个城市配置ID,城市配置里记录该城市的平台优先级顺序。取路由规则时,优先读门店自己的配置,没有则读城市配置。

优先级与降级

确定了候选平台列表之后,按优先级逐个尝试派单。第一个平台派单成功就结束,失败了才试下一个。

// 按优先级顺序逐个尝试
for (DeliveryPlatform platform : priorityList) {
    DeliveryResult result = tryDispatch(order, platform);
    if (result.isSuccess()) {
        return result;
    }
    log.warn("platform {} dispatch failed, fallback", platform.name());
}

这里有个取舍。如果每次都一路试到最后,极端情况下会串行调用多个外部接口,总耗时可能很长。实际处理时,对失败原因做了区分:如果是平台返回「地址不在覆盖范围」,才降级到下一个;如果是超时或网络异常,判断为暂时性故障,可以重试同一个平台,也可以选择直接降级。

降级后全部失败,系统记录失败原因,同时发短信告知运营同学,让他们人工介入或临时调整该门店的路由配置。

平台路由与降级策略图.png

各平台对接的统一抽象

对接了顺丰、京东、美团、达达等多个配送平台,每个平台的接口规范、签名方式、状态码都不同。如果每个调用方都直接面向具体平台编程,后续扩展或替换平台的成本会很高。

抽象出统一的配送服务接口,每个平台实现这套接口:

public interface DeliveryPlatformService {
    String platformCode();
    DeliveryResult createOrder(DeliveryOrderRequest request);
    DeliveryResult cancelOrder(String platformOrderNo, String reason);
    RiderPosition getRiderPosition(String platformOrderNo);
}

调度层只和这个接口交互,不感知具体平台的差异。每个平台的实现类处理自己的签名、请求格式转换、返回值解析。新接一个平台,只需要新增一个实现类,路由配置里加上这个平台的代码,完成。

各平台的账号配置(密钥、接入地址、商家编码等)通过配置中心管理,不同环境(测试/生产)用不同配置,代码里不硬编码任何账号信息。


配送状态回调与同步

骑手被分配之后,整个配送过程有一条状态机:已确认 → 骑手到店 → 骑手取货 → 配送完成。各平台会在状态变化时主动回调我们的接口。

每个平台的回调格式和状态码定义各不相同,需要做一层映射,把各平台的状态码统一转换成内部状态枚举,再写入订单配送记录表。

// 内部统一状态
CONFIRMED   // 已确认/骑手接单
ARRIVED     // 骑手已到店
DISPATCHED  // 骑手已取货
COMPLETED   // 配送完成
CANCELLED   // 已取消

回调处理的幂等性要保证。各平台的回调可能重发,同一个状态可能推多次,处理时检查当前状态是否已经更新过,避免重复写入和重复触发下游逻辑。

回调收到之后,除了更新内部状态,还要通知上游的订单服务和用户侧(推送、小程序消息通知等)。这里用消息队列异步处理,回调接口只负责接收和入库,后续通知动作走消息消费,保证回调接口响应速度,也避免下游故障影响回调处理。

骑手实时位置是另一个维度的数据。用户在小程序上看骑手在哪,需要轮询或长连接查询。各平台提供的骑手位置接口也做了统一封装,统一返回经纬度坐标,调用方不感知具体平台。


补单机制

配送过程中有一类场景需要专门处理:骑手已经取货,但系统里的配送单出了问题(平台侧丢单、状态异常等),需要重新发起一次配送请求,也就是补单。

补单和正常派单的区别在于,补单用的订单号要带特殊标识,避免和原单冲突。平台收到带标识的订单号,识别为重发,会和原单关联处理,不会产生重复运费结算。

门店端提供了手动补单入口,补单操作在后台记录一条补单日志,方便后续对账。如果同一订单多次补单,每次的重试序号递增,订单号后缀也跟着变化(如原单号+-1-2),保证每次请求对平台来说都是唯一单号。


连锁门店的几个实际问题

运力不足时的告警。高峰期某个城市的配送平台骑手不够,派单成功率下降,系统要能感知到这件事。实现上,对同一城市在一定时间窗口内的派单失败次数做统计,超过阈值触发告警,推给运营值班群。不能等门店打电话来反馈才知道。

出餐后叫骑手的时机。理想状态是餐做好了再叫骑手,骑手到的时候餐刚好出来。但出餐时间每次都不一样,叫骑手太早骑手等在店里、叫太晚骑手要等出餐。目前的做法是支持配置一个「提前叫单时间」,在预计出餐前N分钟自动发起派单请求,这个N可以按门店类型或高峰时段配置。

预校验与正式派单的分离。用户加购物车选地址时,触发一次轻量的运力校验(只检查距离,不调配送平台接口),给用户即时反馈这个地址能不能送。正式派单在出餐后才调配送平台接口,两个时间点之间间隔可能有几十分钟,中间运力覆盖情况可能变化,所以两次校验是分开的,不能用预校验的结果代替正式派单时的判断。


小结

这个系统设计上没有特别复杂的地方,难点在于连锁场景下几个问题的叠加:多城市多平台的路由决策、外部平台接口的异构适配、运力校验的延迟控制,加上线上出问题时的可观测性。

路由策略做成可配置是绕不开的,运营需要根据各城市的实际运力情况随时调整,不能每次都改代码发版。但可配置不等于配置项越多越好,配置结构要让运营人员看得懂、改得了,过于复杂的配置最后会变成没人敢动的黑盒。

过滤链这个设计值得在类似场景里推广。多个校验条件、不同耗时、需要组合判断,过滤链比一堆if-else好维护,也容易扩展。条件的顺序排列有讲究,耗时短的条件放前面,能快速拒掉大部分无效请求,减少不必要的外部调用。

还有一点是回调处理的稳定性。配送平台的回调不总是准时到,也不总是只推一次。幂等性和消息队列的配合,是保证这块逻辑在生产环境下稳定运行的基础。


最近在知乎出了

  • 「应付6000万会员的秒杀系统专栏」
  • 「几亿用户,百万并发的C端商品系统实战」
  • 「技术团队DDD领域驱动设计三年落地实战」

专栏,感兴趣的可以订阅一下。至于知识星球的,可以搜:

  • 老码头的技术浮生录

它是一个能实际帮你解决难题的星球。有问题的,找知心的Sam哥,支持无限次语音一对一解决你遇到的难题。「另外后续我新写的所有对外的付费专栏,在星球内都是免费的,且可以拿到所有源代码。」

当前星球里免费看的专栏是:

  • 「几亿用户,百万并发的C端商品系统实战」
  • 「技术团队DDD领域驱动设计三年落地实战」

知识星球内后续将推出20+个付费专栏,覆盖电商全链路:

选购线用户会员营销线中后台
购物车服务营销系统订单系统
商品服务用户系统支付系统
菜单服务结算服务

从前台选购到中后台结算,星球成员全部免费,后续新增也不额外收费。

我的知乎账号:

  • SamDeepThinking