做外卖系统也有几年了前后手上过两套配送类项目的源码一套是区域性的餐饮外卖平台另一套是连锁品牌自营的配送中台。我越来越觉得外卖系统源码这东西网上开源的一大堆能跑通的也不少但真正能把“从下单到配送”这条主链路讲明白、把为什么要这么设计的逻辑说清楚的真不多。这篇文章我打算换个方式不讲流水账式的代码讲解而是把一套典型配送外卖系统的整体架构从骨架到肌肉拆开给你看。核心会落在三个词上配送外卖系统源码的整体分层长什么样、下单到履约的主链路技术实现、以及每个关键环节背后的取舍与坑。无论你是刚接触外卖系统源码的开发者还是已经在做订单、调度相关业务的工程师这篇都能给你一张比较完整的地图。我会尽可能地用实际项目里的场景来说话不整虚的。1. 整体架构全貌外卖系统的主链路与分层设计1.1 先理清主链路一次外卖订单的完整旅程拿到一套外卖系统源码我习惯先不看细节代码而是先顺着一条订单走一遍把涉及的模块全部标记出来。一次完整的配送外卖用户侧感知到的就是“下单——等待——收到餐”但系统内部走的是完全不同的另一条路。从技术上拆解主链路大概是这样的用户在小程序或App里选好商品、提交订单订单服务先创建一条待支付订单用户支付成功后支付回调触发订单状态变更同时把订单推送给商家端商家在限定时间内接单接单后系统进入调度环节调度中心根据订单位置、骑手位置、负载情况算出最优指派方案骑手接单后到店取餐开始配送用户侧实时看到骑手轨迹直到送达、订单完成。这条链路牵扯到的核心服务少说有七八个用户服务、商品服务、订单服务、支付回调、商家服务、调度服务、骑手服务、位置服务、消息推送。任何一个环节卡住直接影响用户体验。所以看源码时如果只看某一个模块很容易迷失——真正难的不是某个接口怎么写而是这些服务之间怎么协作。1.2 系统分层的逻辑每一层都在解决什么一套成熟的外卖系统源码在架构上基本都有清晰的分层。我习惯把它分成五层接入层负责所有客户端入口包括用户端、商家端、骑手端、运营后台这一层主要做协议适配、登录态校验和权限控制。网关层是统一入口做路由、限流、灰度、鉴权。业务服务层是核心按领域拆分为订单、商品、商家、用户、调度、配送、结算等服务。基础组件层提供消息队列、缓存、分布式锁、任务调度、实时推送这类通用能力。数据层包含MySQL、Redis、Elasticsearch、对象存储、时序数据库等。这个分层的本质是把“变化”隔离开。比如用户端和商家端对订单数据的读法完全不一样但向下依赖的都是订单服务暴露的标准接口。谁出了问题都不会直接拖垮其他模块。很多外卖系统源码看起来乱问题就出在分层不彻底订单服务里直接写了推送逻辑、调度逻辑后面改一处崩一片。我特别想强调一个点调度和履约建议独立成服务。在最初那版项目里调度逻辑是塞在订单服务里的结果每次大促或者恶劣天气订单服务一抖动整个派单链路全堵死。后来把调度拆成独立服务通过消息队列接单订单服务只负责写单、改状态调度服务负责算、负责推——两者通过MQ解耦之后稳定性提升非常明显。这一点如果你在选型外卖系统源码一定要优先看它是不是这么设计的。2. 下单链路从点击“提交订单”到订单落库的完整技术实现2.1 一次提交动作背后的多步协作用户在下单页点那一下“提交订单”背后是一连串操作。我拆开给你看这也是外卖系统源码里最容易出彩也最容易出bug的一段。首先是预校验。用户Id、商家Id、店铺营业状态、配送范围、购物车快照、商品库存——都会在这一步做检查。注意这里不是简单的查一次数据库而是要尽量把校验前置到缓存层。商品库存放在Redis里店铺开关状态放在缓存里配送范围通过Geo接口实时算一次。这样做的目的很直接减少对数据库的查询压力避免下单高峰把所有流量都打到MySQL上。校验通过后订单服务开始创建订单。这里有个关键动作生成订单快照。什么意思就是用户下单那一刻他看到的商品名、价格、规格、图片全部复制一份到订单明细里后续商家改价、改描述都不会影响已有订单。很多外卖系统源码在这个细节上做得很粗糙订单明细直接查商品表结果商家第二天改了价格历史订单的金额都跟着变对账直接对不上——这问题在真实运营里非常致命。然后是写订单主表和明细表。订单状态初始化为“待支付”。同时在Redis里写入一条预扣库存的记录把用户购买的商品数量从可用库存中扣减掉。为什么是预扣而不是下单才扣因为如果不预扣极端情况下用户下单但是支付失败库存被其他用户买走了等到支付成功时商家已经没货只能取消订单体验非常差。最后一步是异步通知。订单服务发送一条“待支付订单创建成功”的消息给消息队列由下游的支付服务、营销服务、数据埋点服务各自去消费。这里用MQ而不是同步调用的原因很简单下单链路要快不能因为某个非核心组件慢就拖住整个下单流程。2.2 超卖问题的三道防线超卖这个词做过电商类系统的都懂库存只有5份结果卖出去了8单。外卖场景也一样只不过它的库存维度更复杂既有菜品库存又有商家的同时接单容量。我处理这个问题的方案是三层防护缺一不可。第一层是Redis预扣。以商品Id 规格Id为维度在Redis里维护可用库存数量用Lua脚本保证“检查库存是否充足——扣减库存——返回结果”三步的原子性。Lua脚本的好处是可以把多个操作放在Redis服务端一次执行不会出现并发下多个请求同时读到同一个余量的问题。第二层是数据库乐观锁。订单落库时在商品库存表上执行一条“update ... set stock stock - #{num} where product_id #{id} and stock #{num}”的语句用受影响行数判断是否扣减成功。这里不用悲观锁的原因是为了避免行锁等待拖垮数据库性能毕竟外卖系统峰值时下单并发非常高。第三层是消息队列异步对账。哪怕前两层都过了极端情况下还是可能出现缓存与数据库不一致比如缓存更新失败但DB扣减成功。所以订单创建后会发一条消息到对账队列由对账任务定期扫描“Redis预扣记录”和“数据库实际扣减记录”的差异发现不一致就触发补偿。第三层不一定能完全避免超卖但能兜住脏数据保证最终一致性。这三道防线在源码里的体现就是别在业务代码里硬写一些“if stock 0 then stock stock - 1”的逻辑而是应该看到封装好的库存服务接口。2.3 分布式事务订单、库存、结算之间怎么保持一致外卖系统天然是分布式系统一个下单动作涉及订单服务、库存服务、营销服务、支付服务不可能用本地事务把所有数据包在一个事务里。很多外卖系统源码里最难看懂的就是这段明明看起来是同一个订单操作为什么要拆成那么多消息、那么多回调。这里的关键设计是“本地消息表 消息队列”的最终一致性方案。订单主流程在订单库里写完订单后同时往本地消息表里插入一条事件记录比如“ORDER_CREATED”然后通过一个定时任务把本地消息表里的消息扫描出来投递到MQ。下游的库存服务、营销服务消费到消息后再执行自己的业务。如果消费失败MQ的重试机制会让消息反复投递直到下游确认成功。分布式事务的取舍在于外卖系统极少需要强一致性——用户支付成功后订单状态允许有几百毫秒到几秒的延迟才从“已支付”变成“待接单”但不能出现订单状态和用户实际支付结果长期不一致。所以用最终一致性足够满足业务需求而且性能比强一致性的方案好很多。我踩过一个非常典型的坑支付回调已经成功订单状态却还在“待支付”用户疯狂催单客服后台也查不到原因。最后排查出来是支付回调服务在处理通知时本地事务先更新了订单状态再发送MQ消息但MQ发送失败后被吞掉了异常导致下游商家端永远没收到新订单通知。后来改成先写本地消息表再确认提交本地事务由定时任务投递消息后这类问题就基本绝迹了。看源码时重点看它不是“先DB后MQ”而是“先DB和消息表一起提交再异步投递”。3. 订单中心建模与状态机核心数据结构和流转约束3.1 订单核心表的设计思路订单是外卖系统的核心数据订单表设计得合理后面的统计、对账、售后都顺设计得不合理拆表迁移时能折腾到崩溃。给你看一个我常用的订单主表精简结构CREATE TABLE order_main ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 业务订单号展示给用户/商家/骑手, user_id bigint(20) NOT NULL COMMENT 下单用户ID, merchant_id bigint(20) NOT NULL COMMENT 商家ID, rider_id bigint(20) DEFAULT NULL COMMENT 骑手ID未派单时为空, order_status tinyint(4) NOT NULL COMMENT 订单状态见状态机, pay_status tinyint(4) NOT NULL COMMENT 支付状态0未支付 1已支付 2已退款, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额单位元, delivery_fee decimal(10,2) NOT NULL COMMENT 配送费, address_snapshot text NOT NULL COMMENT 收货地址快照JSON格式, expect_arrive_time datetime DEFAULT NULL COMMENT 预计送达时间, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_merchant_id (merchant_id), KEY idx_rider_id (rider_id), KEY idx_status_create_time (order_status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT外卖订单主表;这张表有四个设计点值得你仔细品味。一是order_no与自增主键分离自增id只做内部关联用对外展示和所有接口调用都用order_no避免暴露真实订单量也方便后续分表。二是address_snapshot这种冗余设计你把收货地址整个序列化存进去而不是存一个地址id去关联用户地址表——因为用户改了默认地址不影响已经下单的订单。三是配送费作为一个独立字段它属于订单维度不属于商品维度。四是ride_id用可空字段标记骑手很多源码这里会单独建一张配送单表其实两种方案都行关键看派单模式复杂度。如果系统里还有多人拼单、转单、帮送这些业务拆一张配送表会更清晰。订单明细表我一般单独拆核心字段就几个订单号、商品Id、商品名称快照、商品图片快照、单价快照、数量、小计金额。记住一个原则明细表里存的商品信息永远是快照不是引用。这个原则能帮你避开大量售后纠纷时数据对不上的问题。3.2 订单状态机的设计与流转约束订单状态是外卖系统最容易被写乱的模块。我记得最早写的时候就吃过亏——用户取消订单和商家拒单都能让订单进入“已取消”状态但取消原因、退款路径完全不一样没有约束的随意改状态后台统计直接就乱了。我的做法是先定义一张完整的订单状态机明确每个状态能往哪些状态流转初始化下单状态10待支付用户支付成功进入状态20已支付待商家接单商家确认接单进入状态30制作中出餐后进入状态40待骑手取餐骑手取餐后进入状态50配送中用户确认收货或系统自动确认后进入状态60已完成。任何状态下用户都可发起取消状态70已取消商家接单前后取消逻辑不同接单前取消不需要商家同意接单后取消需要走售后流程。如果配送超时或用户申请退款可以进入状态80售后中。在代码层面状态流转不是靠业务代码里随手setOrderStatus就能实现的而是收敛到一个状态机服务里先校验当前状态是否允许迁移再执行迁移动作并写入状态流转日志表。这个日志表很重要线上排查“为什么订单状态不对”时它是唯一的线索来源。很多外卖系统源码里没有这个日志表排查问题难度直接翻倍。3.3 高并发下的订单存储方案外卖订单有明显的时间聚集特征午餐高峰11点到13点晚餐高峰17点到20点其他时段相对平缓。如果订单量到了一定规模单表存所有订单肯定会出问题慢查询、锁竞争接着就来了。分库分表基本是必选项。常见的分片策略是按订单维度分片以order_id或user_id为分片键用一致性哈希或者直接对分片数取模路由。我见到的外卖系统源码多数用user_id做分片键因为订单查询绝大多数是“查某个用户的历史订单”按用户聚合天然友好。缺点是一个用户的所有订单都落在同一片里如果出现某个超级用户大促时刷大量订单那一片会有热点问题但这种情况对绝大多数外卖场景可以接受。还有一类查询也要提前考虑就是商家端和运营端按商家Id查订单、按时间段聚合统计。这种查询如果直接打到分片后的库上必须走中间件做聚合性能和复杂度都可想而知。所以成熟方案会把订单数据通过binlog或MQ同步到Elasticsearch或ClickHouse查询走搜索引擎订单库只管事务性读写。这是我在外卖系统源码里比较看重的一个设计——写主库、读搜索引擎CQRS的思路。4. 调度与配送链路骑手是怎么被派出去的4.1 抢单、派单还是混合模式外卖调度是整条链路里技术和业务结合最深的部分。三种派单模式各有各的适用场景也是选型外卖系统源码时必须先明确的问题。抢单模式是早期外卖平台的主流。商家出餐后系统把订单广播到附近骑手端骑手自己抢。实现起来相对简单——把订单信息、取餐位置、送达位置、配送费、距离、预估时间推给附近骑手谁先抢到算谁的。但问题很明显骑手只挑顺路、单价高的单偏远订单、恶劣天气订单没人接用户体验不稳定。派单模式是系统说了算。调度中心收集所有在线骑手的位置、当前负载、方向、历史配送速度再结合新订单的位置、时效要求、配送难度算出一个综合评分把订单指派给评分最高的骑手。实现复杂度比抢单高一个数量级但用户体验稳定得多。成熟平台用的基本都是派单或者派单为主。混合模式就是两者结合先系统派单给骑手15秒到30秒的响应时间不接或超时未响应订单进入公共抢单池由附近骑手抢单。混合模式在实现上相当于把两种模式都做了好处是既保证常规时段的确定性又能兜底异常场景。我看源码时最关注的是调度服务有没有把“算得出”和“发得出去”分开。算法模块负责算下发模块负责通过长连接推给骑手端。如果耦合在一起中间任何一个环节抖动都可能导致订单卡死在调度队列里。4.2 ETA与路径规划预计送达时间是怎么算出来的不夸张地说ETA预计送达时间是外卖系统里用户感知最强、最容易引发客诉的技术点。一个外卖系统源码的调度算法可以不那么高级但ETA如果总是飘用户和骑手两边都会炸。ETA的计算我拆成三段来看商家出餐时间、骑手到店时间、骑手送达时间。出餐时间不同品类、不同时段差异很大一般用商家历史平均出餐时长加上一个动态修正系数比如午高峰要乘1.2雨天乘1.3。骑手到店时间依赖地图服务用骑手当前位置到商家的导航距离除以历史均速注意这里不是用直线距离因为直线距离在小区、写字楼密集的城区误差太大。骑手送达时间是骑手从商家到用户的导航时间再加上上楼时间小区楼层、有无电梯会影响这个参数。在技术选型上外卖系统基本都会接入高德地图或腾讯地图的路径规划接口。但单纯依赖第三方地图ETA是不行的因为地图不知道哪个小区门禁严、哪个写字楼取餐要排队。所以成熟的系统会在第三方ETA基础上做“历史修正”——采集每个商家、每个小区/写字楼的历史履约数据训练一个修正模型。刚开始起步的团队可以用一个简单方案定期统计每个POI点的历史平均配送耗时覆盖地图ETA的偏差。这里有个实现细节值得留意ETA是用在“给用户展示”和“给骑手排单”两个场景的两者的口径必须一致。如果用户端展示预计30分钟送达但调度系统内部按20分钟去卡时效骑手会被逼到极限出事故的概率直线上升。4.3 实时位置追踪与消息推送链路骑手端App会持续上报位置一般每3到5秒上报一次GPS坐标。后端收到后要做两件事写入位置轨迹存储用于轨迹回放和异常分析同时把最新位置推送给用户端让用户看到骑手在移动。位置上报和推送这一段的架构外卖系统源码里通常涉及三个组件接入位置上报的HTTP或TCP长连接服务、处理轨迹数据的实时流、推送消息的WebSocket长连接通道。我记得早期做过一版用普通HTTP轮询的方案用户端地图每3秒拉一次骑手位置效果非常差——服务器压力大而且位置更新不平滑用户看到骑手的位置是一跳一跳的。后来的成熟方案是骑手端和用户端都维持WebSocket长连接骑手位置变更时推送服务直接把新坐标推给与该订单绑定的用户通道延迟能控制在1秒内。注意推送服务要按orderId做订阅分组用户端一进入订单详情页就订阅“order_location_{orderId}”这个topic离开页面就取消订阅避免无关消息占用带宽。轨迹回放这块如果数据量不大直接存MySQL就行超过千万级建议上时序数据库。我之前处理过一个定位漂移问题骑手明明在A栋等电梯地图上显示他在隔壁B栋楼顶这种就是GPS漂移。解决的思路是保存原始坐标用于回放同时做一层“路网纠偏”把坐标点吸附到最近的可行道路或POI上这也是接入地图服务时一并可以拿到的能力。5. 高并发与削峰填谷外卖系统扛住高峰期的保命手段5.1 外卖流量的低谷与高峰明显到什么程度外卖行业的流量曲线非常有特点一周里工作日午晚高峰、周末全天相对平均一天里11点到13点和17点到20点贡献全天大部分订单。这个曲线导致系统设计不能按平均值来必须按峰值来。如果峰值是平均的5倍而你按平均值去部署高峰期必挂。我做一个数据对比给你感受下某城市区域平台平峰时段每秒下单量可能只有几十单但午高峰瞬间能冲到每秒上千单。这些请求不仅全都打在订单服务上还要同时查询商品信息、店铺状态、营销活动、地址解析、配送费计算可能一张单背后要触发几十次下游调用。不提前做限量和削峰数据库连接池最先被打满然后服务间互相等待整个系统雪崩。5.2 消息队列怎么削峰把同步流量变成异步缓冲削峰填谷最直接的手段就是消息队列。用户提交订单后订单服务把请求先写入MQ再由下游消费者按照自己能够承受的速度去处理。这个过程跟水库蓄洪是一个道理洪水来了先蓄起来下游慢慢放。但这里有个容易误解的点下单这个动作本身不能完全异步。用户点了提交如果系统只是“收到啦稍后处理”用户根本不知道成没成功。所以下单链路里只有一部分操作可以异步化——比如优惠券核销、积分计算、发送通知、更新搜索索引。核心的“创建订单预扣库存返回支付链接”必须是同步的否则用户无法完成支付。实际架构是同步做核心操作、异步做非核心操作两条腿走路。以订单创建后推送商家为例这一环节非常适合MQ。订单服务写入订单状态后发一条“订单待接单”消息到MQ商家端长连接服务消费消息后推送给商家App。如果商家服务此时有点抖动消息会在队列里排队等它恢复后继续消费不会丢失。削峰的关键就是这些“晚一点处理不会出大事”的操作统统挪到MQ里去排队。5.3 限流与降级高峰期最怕的不是流量大是响应慢削峰是让峰值流量平滑地过去但流量超过系统处理上限时还得有第二道防线——限流。外卖系统源码里最常看到的限流实现是基于Redis的令牌桶或漏桶算法你可以用Lua脚本在Redis里维护一个令牌桶每个请求进来先尝试拿令牌拿到就继续拿不到直接返回“系统繁忙请稍后再试”。限流要按接口和维度拆细。用户维度的限流防止单个用户脚本刷单商家维度的限流防止某个爆款商家把所有流量吸走网关维度的总限流保护整个系统不至于被打垮。限流的阈值不能拍脑袋定最好根据压测数据反推比如压测发现订单服务单机吞吐量是200QPS那集群10台机器时网关限流设一个相对保守的值。降级是限流之外的兜底方案。高峰期时如果营销服务响应变慢订单服务不应该死等它而是直接跳过营销计算按原价下单如果配送费计算服务挂了就按最低配送费或者预先配置的兜底配送费如果用户端的积分签到接口超时就直接返回不展示。降级的核心原则是不影响主链路宁可少算一笔优惠不让用户下不了单。这点在外卖系统源码里要重点看它是不是有开关系统能不能低成本地人工触发降级。6. 常见问题排查与实战复盘6.1 订单状态不一致消息丢失与幂等消费订单状态不一致是我在外卖系统项目里遇到的最多的一类问题表现形式也很统一用户支付了订单还在待支付骑手送达了订单还在配送中商家退款了用户端订单还是已完成。究其根源绝大多数是消息在传递过程中出了问题或者消费逻辑不是幂等的。排查这类问题我的第一反应是查订单状态流转日志表。从日志里能看到订单最后被谁、在什么时候、从哪个状态改到的哪个状态直接就能锁定是哪一环没执行成功。如果日志显示支付回调已经改了状态但推送商家失败那就看MQ的重试日志和消费失败日志定位是网络原因还是消费逻辑抛异常。消息重试也容易引发重复消费问题。比如库存服务消费“订单创建成功”消息时第一次消费成功了但返回时超时MQ会重新投递同一笔订单就会扣两次库存。解法是消费端必须做幂等最简单也最有效的做法是维护一张“消费记录表”以消息唯一Id或业务主键做唯一索引处理前先插入插入成功才执行业务逻辑。这也是我在看外卖系统源码时最看重的一个代码细节凡是消费MQ消息的地方必须有幂等判断。6.2 骑手定位漂移的处理思路定位漂移这个问题开发时不容易发现一上线就暴露尤其在密集的城市区域高楼反射、隧道遮挡都会让GPS漂移几百米。用户端地图上骑手的位置突然跳到隔壁街然后过几秒又跳回来这个体验非常糟糕。处理定位漂移我现在的方案是“原始坐标入库 展示坐标纠偏”双轨制。骑手的原始GPS先落到轨迹存储里然后进入一个纠偏管道纠偏逻辑用地图SDK提供的坐标纠偏能力配合历史轨迹点做平滑处理。核心思路有三个一是过滤掉明显偏离路网的坐标点比如连续坐标点的速度超过合理骑行时速判定为漂移点直接丢弃二是使用卡尔曼滤波对坐标序列做平滑让轨迹不是生硬的折线而是相对自然的曲线三是在展示层做插值用户端看到的位置不是“下一次上报点”而是基于最新两个有效点之间做线性插值出来的位置视觉上要平滑得多。这里面最容易犯的一个错误是直接用纠偏后的坐标去做配送距离计算。漂移是真实存在的但配送里程结算、距离补贴应该基于原始坐标算否则骑手吃了亏一定会投诉。我在一版项目里就是因为直接用纠偏轨迹算了配送距离导致一批骑手的距离补贴少了客诉率直接升高后来改成“纠偏只用于展示、结算用原始数据”才算解决。6.3 午高峰订单积压的一次完整复盘再说一个亲身经历的事故当时对我们的架构调整影响挺大。某天午高峰突然接到大量用户反馈订单一直停在“待支付”或者“支付中”商家端不停报“收不到新订单”客服系统被问爆。我当时的第一反应是看MySQL监控订单库的CPU、连接数都爆了SQL慢查询日志里全是订单表的写操作。继续追发现罪魁祸首是一场营销活动。当时业务上线了一个大额优惠券活动用户领券后下单时订单服务要同步调用营销服务验券、计算优惠金额但营销服务的缓存没有提前预热导致活动开始后大量请求穿透到数据库营销服务的RT从20ms涨到了800ms。订单服务是同步调用营销服务的可用线程被这些慢请求全部占满新订单进来没有线程处理数据库连接也因为等待被耗尽最终导致下单链路整体阻塞。那次事故之后做了一个很重要的结构调整订单服务调用营销服务改成异步降级核心下单链路不依赖营销计算结果。具体方案是下单时先用最快的速度算出“最差价格”不使用优惠的价格然后通过MQ异步补算优惠并更新订单金额。用户端看到的提示是“支付金额以最终账单为准”优惠计算成功后再推送一条最终金额变化通知。这个方案牺牲了一点体验但保证了下单核心链路的稳定性。往后遇到类似促销场景架构上都遵循一个原则核心链路绝不依赖非核心服务的同步结果。复盘下来的教训很直观外卖系统源码的架构再好也架不住线上流量场景和业务需求的反复变化。不是代码写得多优雅就完事了必须考虑每个接口在峰值下的表现以及核心链路对下游故障的容忍度。7. 写在最后的几个实操建议这套架构拆下来我自己心里最有感触的一点是外卖系统表面上是技术问题实际上很多设计决策都是被业务逼出来的。超卖是因为真有那么多人同时抢状态机是因为订单流转真的有那么多分支ETA是因为用户真的会在三分钟内问十次“还有多久到”。很多时候不是要设计得多复杂而是要顺着业务需求把该想到的边界都想到。如果你现在正准备上手一套配送外卖系统源码从哪看起呢我建议不要从小程序端代码看起也不要从管理后台看起找一个测试订单从下单接口一路跟到订单完成把中间所有状态变更、消息发送、任务调度全部标记出来一张主链路图先画出来再往细里钻。这个流程走通之后你对这套源码的理解会秒杀掉绝大多数直接看代码的人。最后分享一个小技巧拿到任何外卖系统源码先全局搜索一下消息队列的topic定义再把所有消费者类列出来。一个系统的业务边界清不清晰从topic命名和消费者归属就能看出一大半。如果topic命名混乱、一个服务里什么消息都消费那这个系统后续维护大概率会很难受如果topic清晰、消费者职责单一哪怕代码风格差一点架构也都还救得回来。这个判断方法比看几十个类文件管用多了。