ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从业务逻辑出发拆解外卖系统

从业务逻辑出发拆解外卖系统 从业务逻辑出发来拆解外卖系统做产品或者做技术的人迟早会遇到一个绕不开的系统——外卖系统。我第一次完整接触外卖系统的业务逻辑时第一反应是“这不就是电商加个配送吗”真深入下去才发现外卖系统比纯粹的电商交易要复杂得多。它有三个强实时性角色的协同、有严格的时间窗口、有动态的供需匹配还要应对各种“人”带来的不确定性用户会等不及取消、商家会漏看单、骑手会爆胎。任何一个环节出问题状态的流转就全乱了。这篇文章我想换个角度不直接上来画架构图、列技术栈而是先从业务逻辑出发把外卖系统从一单完整的交易开始拆到每一环的决策逻辑、状态变更和异常处理。适合正在做外卖类产品的新手PM、刚接手外卖系统的后端开发以及想搞懂“系统里的人是怎么协作的”的创业者。搞懂了业务逻辑技术选型和架构设计只是水到渠成的事。1. 一单外卖背后到底发生了几件事1.1 外卖不是“点餐配送”这么简单很多第一次接触外卖系统的人会下意识地把外卖理解成“用户下单商家出餐骑手送过去”这个理解没错但它停留在物理世界。数字化系统要做的事情比这多得多。物理世界里的每一步在系统里都需要被拆成时间点、事件、状态、责任人、补偿机制。举一个最简单的例子。用户点了一份盖浇饭“下单”这件事在系统里不是一个动作而是一连串的动作用户提交订单系统要校验地址是否在配送范围内、店铺是否营业、餐品是否在售、库存是否够、用户账户是否正常、优惠券是否可用通过校验之后要锁定库存、创建支付单、拉起支付支付成功回调之后才真正生成一条“待商家接单”的订单推送给商家端。同样一单配送也不是“骑手从A到B”。它包含了骑手接单、到店、报备、取餐、确认取餐、开始配送、到达、确认送达每一个动作背后都对应一个GPS位置采集点、一个状态变更、一个给用户的推送通知。这些动作组合起来才是完整的订单生命周期。所以拆解外卖系统的正确姿势不是先看有几个微服务、用了什么消息队列而是先把“一单外卖从产生到完成整个过程在真实世界里会发生哪些事”完整列出来。业务逻辑不清架构再先进也没用。1.2 核心角色与交易链路的三个关键闭环外卖系统里至少有四个角色在同一个交易链路里协同用户、商家、骑手、平台。每个角色都有自己的核心诉求系统存在的意义就是平衡这四方的利益。用户的诉求是“快、准、稳”——餐出得快、送得快、别送错、别撒漏。商家的诉求是“接单效率高、出餐压力小、数据清晰”——最好订单自动接出餐时间预估准。骑手的诉求是“派单合理、路线顺畅、时间宽松”——别让我空跑、别让我爬六楼还得超时。平台的诉求更复杂用户体验要好、商家的供给要丰富、骑手的运力要稳定同时平台自己的成本和收益也要可控。这三个角色之间的交易链路核心是三个闭环交易闭环用户下单 → 支付 → 商家接单 → 出餐 → 骑手配送 → 用户确认收货 → 结算给商家和骑手。履约闭环商家出餐 → 骑手取餐 → 配送中 → 送达 → 异常处理和售后。信息闭环用户看到的餐品信息、商家看到的订单信息、骑手看到的取送信息必须是同一份数据源任何一端的数据不一致立刻引发投诉。把这几个闭环在脑子里过一遍你就会发现外卖系统的核心不是“技术有多牛”而是“状态流转是否可靠、异常处理是否兜得住”。业务逻辑拆解的第一步就是把这三个闭环里的每一个节点讲清楚。2. 订单生命周期贯穿全局的那条主线2.1 从“购物车”到“已支付”用户侧的状态流转订单是整个外卖系统的核心实体订单状态就是系统的“主动脉”。我习惯把订单从创建到完成的状态流转画成一条线再标出每个状态节点上的触发事件和责任人。用户侧的状态通常这么走待支付 → 已支付 → 商家已接单 → 餐品制作中 → 骑手配送中 → 已送达。如果中间出问题还会有已取消、退款中、退款完成、异常单等状态。这个状态序列看似简单但每条转移路径背后都有对应的业务规则。举个最常见的场景用户提交订单进入待支付状态此时系统已经锁定了库存但还没有真正占用商家的产能。超过15分钟未支付系统自动关单库存释放。这个时间窗口不能随便定太短了影响用户支付体验太长了商家侧的订单积压概率增加。大多数平台把这个值设在这个区间还要针对不同场景做动态调整比如高峰期短一些深夜用户可能付款慢。再看看支付。支付成功回调到达系统后订单从“待支付”切到“已支付”同时要触发一系列动作通知商家端有新订单待处理、给用户发下单成功的推送、开始计时商家接单超时。这里有一个非常关键的工程问题支付回调是异步的如果回调丢失或者延迟订单状态就会卡死。所以系统必须有主动查单的对账机制不能只依赖被动回调。2.2 商家接单与出餐平台如何做状态同步订单推到商家端之后商家的接单策略会直接影响订单状态流转。现在主流的外卖平台都有两种接单模式手动接单和自动接单。手动接单适合小店老板看到单子确认一下防备忙不过来自动接单适合连锁和出餐稳定的商家系统接单后直接进入制作流程。商家端的状态流转通常是待接单 → 已接单 → 制作中 → 已出餐。这里最容易被忽略的业务逻辑是“出餐”到底以什么为准。如果商家点了“出餐”但骑手还没到店那餐品只能放在台面上等保温、口感都会受影响如果骑手到了店商家还没出餐骑手就得在店里干等。为了解决这个衔接问题系统里引入了两个时间节点预计出餐时间和预计到店时间。系统根据餐品种类、门店历史出餐效率、骑手距离和路况动态计算两边的匹配度尽量让骑手到店时间与出餐时间重合。这里要特别讲一下出餐上报机制很多新手设计系统时会忽略它。商家出餐后点击“出餐”这个动作不仅仅是改状态它同时会触发骑手端收到取餐提醒、系统重新计算骑手等餐时长、如果骑手还未到店系统会评估是否要改派其他骑手。出餐上报的准确性直接决定了骑手端的调度效率和用户体验所以有些平台会做商家出餐超时的处罚/奖励机制本质就是为了让商家如实上报状态。2.3 骑手配送与妥投履约链路的最后一环骑手端的订单状态比商家端更细待接单 → 已接单赶往商家 → 已到店 → 取餐中 → 已取餐前往用户 → 已送达。这一连串状态里有两个非常考验业务逻辑的点。第一是到店/取餐的报备它涉及骑手与商家的权责切分。骑手点了“到店”之后如果餐没做好等餐时间算谁的如果骑手没点“到店”就直接取餐走了餐品少了算谁的所以系统必须用状态时间戳把这些账算清楚这也是后面结算、赔付的重要依据。第二个点是送达确认。现在主流平台默认“点击送达即视为完成”但在业务逻辑上这里会有恶意提前点送达的风控规则骑手距离配送地址过远时点送达系统会拦截或者标记异常。反过来用户侧收到的状态是“已完成”但实际没收到餐又会有“用户未收到餐”的售后流程。所以“送达”这个状态在系统里并不是一个单纯的订单状态它关系到骑手收入结算、平台服务评分、用户的售后入口。到这一步一单正常外卖在主流程上的状态流转就全走完了。理解了这条主线你再看外卖系统的其他模块都是围绕这条主线的“辅助系统”。3. 业务规则与状态机设计谈清楚“为什么这么设计”3.1 状态机是外卖系统的“骨架”主流程的状态梳理清楚了下一步要落地的就是状态机没有状态机的订单系统就是一团浆糊。状态机的核心价值有两个一是让订单的流转路径确定性明确——每一步都在设定的合法范围内二是让状态变更之后的动作触发有迹可循。设计状态机的时候要注意几个原则。第一状态不能乱跳。比如“已支付”的订单不能直接变成“已完成”中间漏了“商家接单”和“骑手配送”就不行。第二状态变更要有事件来源。每次状态变更必须对应一个触发事件可能是用户操作、商家操作、骑手操作、定时任务或支付回调。第三同一个事件在不同状态下要允许做不同的处理而不是只依赖状态判断。我举一个特别常见的例子取消订单。取消这个动作在订单的每个阶段都有不同的语义和规则。待支付状态下用户取消是无条件的系统直接关单已支付但商家未接单用户取消要进入“退款”流程商家已接单但骑手未取餐用户取消可能会触发“赔付商家”的费用判断骑手已取餐再取消规则就更严格了可能要走客服介入。如果不把取消设计成一个带着业务规则的状态机而只是在代码里硬写 if-else后续每加一条规则都会成为新的故障点。再讲一个容易被忽略的细节状态机必须有超时机制。每个状态节点都可能有“卡住”的情况——用户不支付、商家不接单、骑手不取餐、用户不确认收货。这些节点都要设计定时器去触发兜底动作自动关单、超时提醒、自动改派、自动确认收货。没有兜底的外卖系统会堆积大量“僵尸订单”。3.2 取消、退款、异常单边界情况才是真正考验如果说主流程是高速公路那取消、退款和异常就是泥泞小路但恰恰是这些小路最能考验业务逻辑设计得是否扎实。先说退款。外卖的退款和电商有一个很大的区别外卖有明确的时效性餐品是即时制作的而且部分餐品一经制作就无法二次销售。所以外卖系统退款必须做“实扣实退”的精细化设计用户支付的金额、平台给的优惠补贴、商家承担的折扣每一部分都要算清楚谁承担损失。最常见的场景是用户取消订单系统要给用户退全额但商家已经备料了这部分成本谁来背不同平台有不同的规则本质上是平台和商家之间的博弈而系统要做的就是准确记录每个环节的时间戳和状态为后续判责提供依据。再说异常单。这个大类里包括商家出餐超时、骑手超时未取餐、用户拒收、地址无法送达、餐品撒漏、用户长时间不接电话等等。异常单的最优解不是“发生后处理”而是“发生时快速分流”。系统要根据异常类型自动触发不同的处理流程是继续等待、改派骑手、还是取消订单并全额退款。优秀的业务逻辑在异常发生时就已经能自动判断“该走哪条路”而不是把所有情况都推给人工客服。我给一个建议在设计业务逻辑图的时候把“正常流程”和“异常/补偿流程”分开画。正常流程用于开发主流程的骨架补偿逻辑则单独梳理成规则矩阵两者结合才能支撑起完整的系统。4. 系统的核心模块拆解与数据流4.1 四端联动的协作逻辑外卖系统的业务逻辑决定了它的技术模块边界。按照角色来分系统至少可以拆成用户端、商家端、骑手端、平台管理端加上底层的交易中心、履约中心、结算中心、消息中心。每个端背后都知道“同一个订单”的不同截面。用户端看到的是“我的订单”它关心订单状态是啥、骑手在哪、餐什么时候到。商家端看到的是“待处理订单、制作中、已完成”它关心的是什么时候该出餐、什么时候骑手到店。骑手端看到的是“被派给自己的订单”它关心的是取餐点、送餐点、路线、时间预算。平台管理端看到的则是全部订单的实时状态、异常监控和规则配置。四端的数据必须围绕订单ID做聚合。用户在App上取消订单商家端和骑手端几乎同步就要看到订单状态变化不能出现用户取消了、商家还在出餐的情况。这背后靠的是可靠的消息推送和状态同步机制但从业务逻辑上讲它靠的是“统一状态源、状态变更广播”的设计原则。谁修改了订单状态这个事件必须发给所有关心这个订单的端。这里我有一个很深的体会业务逻辑拆解时最容易漏掉的是“端与端之间的预期管理”。比如骑手取餐后用户端的 Status 还是“商家制作中”用户就会焦虑。所以正常的设计会在关键节点给用户推送状态变化通知甚至提供骑手实时位置轨迹。承载这些体验的背后是每一个状态节点上“要不要通知”“通知谁”“用什么文案”的业务规则。4.2 从创建到归档的数据走向从数据的视角来看外卖订单经历了这样的生命周期创建写入订单表 → 变更状态机驱动修改状态 → 履诺履约过程中产生新数据骑手位置、操作记录 → 结算金额分摊、核算 → 归档进入离线数仓用于数据分析和复盘。在创建阶段订单数据会涉及多个子域的写入订单主表、订单明细表、支付单、配送单、营销使用的优惠明细。这里有一个工程上很重要的问题订单创建和库存扣减必须保持原子性。用户提交订单后扣减库存成功但订单创建失败了库存就丢了订单创建成功但扣减库存失败就会出现超卖。现在比较稳妥的做法是通过本地消息表加事务消息来保证最终一致性但业务逻辑设计上要明确“什么算下单成功”——通常以订单表创建成功为准其他动作异步补偿。在履约阶段更多的数据是流水和轨迹。骑手的GPS位置、每分每秒都在写入位置表这些数据的读压力非常大所以通常会做汇聚和削峰。订单状态变更的历史也要记录下来这就是我们常说的状态变更流水表。这张表太重要了运营要复盘、客服要判责、财务要核账全都看它。我自己做业务逻辑设计时有一个很土但很有效的做法确保任何一条订单状态都能根据流水表和时间字段完整还原出“这个订单在什么时间点经历了什么”。订单完成后进入结算和归档阶段。结算关注的不是订单本身状态而是每笔订单的钱怎么分平台服务费、商家收入、骑手配送费、用户退款和补偿。这些规则可以由产品灵活配置但底层的数据源都是订单上的时间戳、金额字段和状态变更记录。5. 实操中那些让人头大的问题与排查思路5.1 状态不一致最常见也最隐蔽的问题我接手过的外卖系统排查案例里一半以上都跟“状态不一致”有关。典型的表现是用户端显示“支付成功”商家端没有收到新订单骑手端已经点了“送达”用户端还停留在“配送中”用户已经取消订单系统照样给用户推送了“骑手已取餐”。这些问题归根到底都是几个端的数据没有对齐。排查这类问题我的思路分四步。第一步先定位“源状态”和“目标状态”。确定哪个端的状态是准确的、哪个端的同步延迟了。如果可能直接查订单主表的状态以它为准。第二步查状态变更流水。看这条订单最后几次状态变更分别是什么时候、由谁触发的。如果流水显示“已支付”被写入但商家端没收到问题大概率出在消息推送环节而不是订单状态本身。第三步查消息队列的积压情况。很多状态不同步是消息积压导致的骑手端报了位置消息推到队列里半天没被消费用户端的轨迹就卡住了。第四步查并发场景。两个请求同时修改同一个订单状态——比如用户取消的同时骑手点了送达。这时候如果没有做好状态机校验后到达的请求可能直接把状态覆盖掉导致“已取消的订单被送达了”这种脏数据。解决状态不一致的核心思路不是“出了错再修”而是在写入时做合法转移校验、在异步链路里做消息幂等、在关键节点做定时对账补偿。这三层都做好了状态不一致的概率会大幅下降。5.2 超时、并发、库存三个绕不开的坎外卖系统是典型的高并发实时系统尤其在午晚高峰流量会瞬间冲上来。我总结下最常遇到的三个坎。第一个坎是超时。前面讲过很多定时任务和状态超时未支付关单、商家超时未接单提醒、骑手超时未取餐改派。这些定时任务在高峰期同一时刻可能触发几百万条如果全部扫描数据库DB 压力会非常可怕。实际项目中会把任务按时间分桶、用延迟队列触发同时也别一把扫全表尽量用游标分批拉取。第二个坎是并发下的库存。外卖和电商不一样的地方在于它不仅是“总量库存”还涉及分时产能。比如一个爆款单品的当日销量上限是100份商家实际接单能力可能到中午就饱和了。系统的库存模型要考虑“商品维度”和“店铺维度”两层不能只扣菜品库存还要看这家店此刻还有没有产能接新单。扣减时要防止超卖常用的手段是数据库乐观锁update ... set stock stock - 1 where stock 0或者用 Redis 预扣加异步对账。第三个坎是骑手调度并发。再准确的时间预估也顶不住实际的早高峰路况。骑手同时收到多个新订单推送、多个订单同时被申请改派如果调度系统没有做好“同一骑手同一时间只能有一个主任务”这种去重约束就会出现两个订单都派给同一个骑手的冲突。业务逻辑上要设计好骑手的状态机空闲、任务中、忙碌只有空闲状态才能被派单。这些坎不是靠某一个中间件就能解决的更多要靠业务逻辑层面的降级和约束。我在设计系统时始终提醒自己别把期望压在“最优解”上高峰期能保证“不崩、不错、能兜底”就已经是合格的外卖系统了。6. 从“能跑”到“好用”的经验沉淀从业务逻辑出发拆解外卖系统最大的收获不是画出多少张流程图而是理解了系统里每个角色背后的诉求和约束。用户要的是确定感商家要的是效率骑手要的是合理的路和时间平台要的是全局平衡。业务逻辑设计的本质就是在这几个目标之间找那个动态平衡点。最后分享一个我自己踩过的坑早期设计订单状态时只考虑了正常路径漏了“商家接单后但骑手长期未到店”这类边界状态结果上线后每天的异常单处理都靠手工后台。后来在订单状态机上增加了“等待超时”的定时流转并按分钟级巡检才算把这个问题兜住。做外卖系统别追求把所有场景一次性想全但一定要把兜底机制设计好。系统可以允许偶发的业务异常但不能没有应对异常的规则和流程。如果你正在做外卖系统的设计或开发我建议的第一步不是选框架、建仓库而是拿着纸笔把自己当成一个用户在App里完整点一单把每一步看到的、感受到的都记下来再把自己当成商家接一单、当成骑手送一单。三个视角走完业务逻辑的骨架也就出来了。剩下的事情无非是把这套逻辑用代码忠实地实现出来。
返回列表