电商分布式架构设计:服务拆分、高并发与避坑实践
简介这是一份聚焦电商平台分布式架构设计的方案文档适用于正在规划系统扩展路径的架构师、技术负责人及后端开发者。文档以电商业务为背景从架构设计的必要性与前提条件切入系统梳理客户对在线购物、支付、物流、客服、评价等核心需求并给出业务模块拆分与技术架构演进的整体思路。资源为单个docx文件约1.46MB内容结构完整便于按章节阅读。文中覆盖从早期单体服务器到应用/数据库/图片分离、缓存引入、集群与负载均衡再到分布式、消息队列等高级设计的演变过程并结合业务架构图与技术架构图说明各阶段选型理由。已有133人学习下载适合希望理解电商后端架构设计逻辑、为高并发场景做技术准备的读者参考。1. 电商平台分布式架构设计先想清楚为什么要拆再谈怎么拆大多数电商平台走到分布式架构设计这一步并不是流量先把它压垮了而是复杂度先把它拖垮的。业务还在单机时几十个开发往一个工程里叠代码每次发布都要全员待命订单、库存、支付耦合在一个进程里改一行库存字段得拉着三个团队一起回归。这篇要聊的方案就是一套能支撑高并发、可扩展、还能让团队并行开发的运行架构。它适合已经在做单体拆分、准备上微服务或者正在经历大促压测的从业者。做这份设计时我习惯先问一句你想通过分布式得到什么如果答案只是高并发后面大概率会被一致性、链路追踪和运维复杂度反复折磨方案选型也会跟着摇摆。2. 分布式架构的底层逻辑电商业务哪些特性决定了必须分布2.1 电商业务的四个必然高并发、高可用、数据一致性与成本弹性电商和其他业务系统最大的差异是流量模型和链路复杂度。日常一个中小平台的QPS可能只有几千大促瞬间冲到几十万流量曲线像心电图订单、库存、支付、物流、会员之间互相依赖一次下单要跨越五六个服务。这四条核心特性几乎决定了架构设计里所有的重要选择。电商特性业务表现对架构的要求高并发日常流量与大促峰值差距20倍以上无状态服务、水平扩展、限流降级高可用下单/支付链路中断直接影响收入多实例部署、超时控制、熔断数据一致订单、库存、支付跨服务操作同一笔业务分布式事务、幂等设计成本弹性流量波峰波谷明显硬件利用率低容量规划、弹性伸缩这张表是做架构评审时经常用来对需求的。高并发逼着你把服务拆开、把状态外置让任何一台机器挂了都不影响整体高可用逼着你接受部分失败一个服务慢不能拖垮整条链路数据一致性最让架构师头疼因为它会让你在性能和正确性之间反复权衡成本弹性则是容易被忽略的点分布式架构能扛流量但扛不住无意义的资源浪费容量规划必须和业务预测一起做。我见过不少团队把微服务拆得特别细结果日常QPS只有几百机器开了二十台大部分时间利用率不到百分之五这就是只看到了高并发、没算成本账。所以做方案之前先把这四个必然摆到桌面上所有设计决策都可以回到这四条来验证。2.2 向上扩展还是向外扩展服务粒度怎么切才不后悔选扩展方向没有玄学就是一台机器扛不住的时候你是换更大的机器还是加更多机器。单机的CPU、内存、IO总有上限一台64核512G内存的物理机价格翻好几倍性能和成本都不是线性增长所以电商这类高并发业务默认选向外扩展。向外扩展的前提是服务无状态或者状态被外置到缓存、数据库、对象存储里否则扩容只会加剧数据不一致。服务粒度怎么切是架构设计里最容易争吵的地方。我一般先按业务域切用户域、商品域、订单域、库存域、支付域。每个域一组服务域名之间通过接口交互不共享数据库表。这样切的理由有三条第一是数据归属清晰订单表归订单域管其他域要查订单数据必须走接口第二是变更频率隔离商品改版不影响订单发布第三是对应团队边界一个域由一个小组维护符合康威定律。相反如果按功能切比如把下单接口单独拆一个服务、查询接口再拆一个服务服务之间要互相调数据库调用链绕来绕去最后变成比单体更复杂的分布式单体。判断粒度是否合适有个实用标准如果一次业务操作要在五个服务之间同步调用才能完成而你又说不清每个服务独立存在的价值那就说明拆碎了。五个服务演进成三十个服务很容易但每个服务都变成调用链上的黑匣子排查问题全靠日志拼图这是做分布式最直接的代价。2.3 动工之前的检查清单分布式不是免费午餐很多团队做架构设计时上来就画服务拓扑图画完才发现连基础问题都没回答。我这里有一份评估清单是用在各种项目启动会上的至少能把方向拉回正轨。决策项需要回答的问题影响峰值QPS未来半年预期峰值是多少是平时的几倍决定集群规模与限流阈值可用性目标99.9%还是99.99%允许多久不可用决定多活、容灾投入数据量订单/商品三年大概多少条单表能否承载决定是否分库分表团队规模几个人能独立维护一个服务决定服务粒度和数量发布频率每个域多久发布一次能否独立发布决定是否拆分微服务这份清单做完不少团队会发现当前阶段根本不需要完整分布式。业务模式还没验证清楚时我建议先做模块化单体代码分模块、数据库分schema、对外接口明确但部署还是一个进程。等流量和数据量真的到了单机瓶颈再按模块边界拆成独立服务不用推翻重来。还有一点很多人忽略做架构设计前最好先按企业数据架构设计方法把领域模型和数据归属梳理一遍。尤其是订单、库存、商品这些核心域定义清楚核心实体、实体关系和数据生命周期后面做服务拆分、分库分表才有依据。这一步省掉后面每个服务都觉得自己该管库存最后库存数据散落三套对账对到怀疑人生。3. 从单体到分布式的落地路径先拆分流量再拆分服务3.1 第一刀接入层网关的职责边界与限流参数从单体走向分布式的第一刀不应该切服务而是先切流量入口。完整链路通常是DNS做域名解析和就近接入负载均衡做四层转发API网关做七层路由、鉴权、限流和灰度然后流量才进入业务服务。网关的作用是把横切逻辑从业务代码里抽出来不然每个服务都要自己写一套鉴权和限流标准肯定不一致。网关限流是进入分布式之后必须过的第一关。大促时真正的风险不是服务本身扛不住而是服务被打满之前数据库连接池和线程池先耗尽导致所有请求都排队超时。我一般会在网关层做按用户维度的限流。配置里几个参数是必须明确的QPS阈值、突发容量、超时时间。# 网关限流配置参考按用户维度控制请求速率 spring: cloud: gateway: routes: - id: order_route uri: lb://order-service predicates: - Path/api/order/** filters: - name: RequestRateLimiter args: key-resolver: #{userKeyResolver} redis-rate-limiter.replenishRate: 500 redis-rate-limiter.burstCapacity: 2000这里replenishRate是每秒向令牌桶补充的令牌数决定平均放行速率burstCapacity是桶容量决定允许的瞬时突发量。burstCapacity不是越大越好设成2000意味着瞬间能放2000个请求到后端后端如果只能扛1000突发过去就是雪上加霜。限流参数的设定要配合压测结果先压出后端的真实上限再留30%余量反推网关阈值。网关还需要承担超时控制。每个路由都应该配置连接超时和读超时比如连接超时1000ms、读超时3000ms。一旦某个下游服务变慢网关要能快速返回失败而不是一直占用线程等待。很多团队把超时时间设成默认值服务一慢整条链路跟着卡这就是没把网关当成架构的一部分来设计。3.2 第二刀把订单、用户、商品拆成独立服务服务拆分是有顺序的不是哪热闹先拆哪。我一般先拆用户和商品因为这两个域相对独立调用方多、变更频繁拆开之后可以快速验证微服务的发布和隔离效果订单、库存、支付涉及写一致性和分布式事务难度高放到第二批。订单域拆分时要做一个关键决定要不要在订单服务里保存商品快照。如果不保存订单列表页每次都要调商品服务查标题和图片一次列表N条订单就是N次远程调用性能很难看。常见做法是在下单时把商品标题、单价、图片URL冗余到订单表里查询不再依赖商品服务。这违反了数据只归属一个域的原则但电商场景里冗余字段换性能是值得的只要字段是下单时刻的不可变快照不会产生更新冲突。服务拆分后接口版本管理要提前想清楚。订单服务的接口不能今天叫/api/order/create明天改成/api/order/placeOrder下游商品服务、支付服务全要跟着改。我在设计文档里会明确要求所有对外接口带版本号比如/api/v1/order/create接口字段只增不改废弃接口至少保留两个版本周期。这条规范看起来是小事但真到十多个服务相互调用时接口乱改就是发布事故的主要来源。服务之间的调用还需要约定失败策略。读取类接口可以设置快速失败返回降级数据写入类接口要设置合理超时和重试但不能无脑重试——下游已经处理成功、响应丢了重试就会造成重复下单所以写接口的重试必须配合幂等设计这在第4章展开。3.3 第三刀分库分表与读写分离的拆法服务拆完数据库往往是下一个瓶颈。电商最典型的是订单库和库存库。以订单为例单表数据量到千万级别索引深度增加写性能和查询性能都会明显下降。分库分表的第一原则是选对分片键。买家查询订单是最常见的场景所以按user_id分片最合理。// 分片路由规则4个库每库16张表 int dbIndex (userId / 16) % 4; int tableIndex userId % 16; String tableName t_order_ tableIndex;这段伪代码展示的是最简单的取模路由。dbIndex决定请求落到哪个库tableIndex决定落到哪个表同一个用户的订单永远落在同一个分片上。好处是单用户维度的查询只需要访问一个分片不需要跨库聚合坏处是后台运营如果要按seller_id查订单这个查询要扫全部分片根本没法用。解决非分片键查询的常见做法是建索引表或宽表。比如维护一张order_seller_index字段只有seller_id和order_id也按seller_id分片运营端查询先查索引表再回订单表取详情。代价是写入时多一次冗余写入但换来运营查询的可用性。千万级数据量下索引表的异步写入可以通过消息队列来做不阻塞主链路。分片数量也不是拍脑袋定的。我习惯按三年数据量估算假设年订单量2亿三年6亿单表容量尽量控制在1000万以内需要60多张表那么设计4库×16表等于64张表是合理的。注意分片数量要选择2的幂次这样后续扩容时可以用一致性哈希的虚拟节点来迁移数据代价最小。分表数量选成6库×11表这种非2的幂次后面扩容时几乎要重写路由这就是给自己挖坑。读写分离在电商里也要区分场景。商品详情这种读多写少的场景适合主从分离订单和库存写入路径绝对不能走从库主从延迟哪怕只有几十毫秒都可能让刚下单的用户查不到订单投诉电话直接打爆。订单的查询要么走主库、要么走缓存这个红线要在架构设计文档里写明。3.4 异步化改造消息队列到底放在哪一段只要允许把同步调用变成异步系统的抗压能力会上一个台阶。常见的做法是把非核心链路从请求线程里剥离用户下单成功后订单服务发一条消息给消息队列然后由消费者去处理积分、会员等级、搜索索引更新、短信通知这类辅助业务。消息用途生产者消费者可容忍最大延迟订单创建通知订单服务积分/会员服务秒级支付成功通知支付服务订单/库存服务秒级商品变更通知商品服务搜索/缓存服务分钟级消息队列的使用有几个参数要提前定好重试次数、重试间隔、死信队列。消费失败不能无限重试否则会拖死消息队列。我一般设置重试3次间隔按指数退避1秒、10秒、100秒超过后进入死信队列由人工或对账任务处理。这里的核心是定义清楚什么算处理成功消费者拿到消息写完业务数据提交offset才叫成功如果业务写了一半应用宕机消息会被重新投递所以消费者必须实现幂等这个点和第4章紧密相关。还有一类消息不能异步那就是库存扣减。如果下单流程先返回成功、再异步扣库存库存不足时订单已经创建了后续要么自动取消订单要么超卖。要异步削峰就得用缓存预扣减库存加异步落库的机制这个在秒杀场景里再细说。异步化的原则一句话能异步的都异步但涉及资金和库存的最终一致链路必须有兜底对账。4. 核心场景的分布式设计订单、库存、支付与定时任务4.1 分布式事务选型本地消息表、TCC与SAGA怎么权衡跨服务的写一致性是分布式架构设计里最绕不开的话题。订单创建要扣库存、支付成功要改订单状态、下单要加积分每个环节跨一个服务数据库本地事务管不到别人家。解决方式不能一上来就上强一致电商的高并发场景强一致往往意味着低吞吐。方案一致性类型性能影响实现成本适用场景本地消息表最终一致低中订单创建后发通知、积分累计TCC最终一致中高资源预留类如库存冻结SAGA最终一致中高长流程编排如下单到出库2PC/XA强一致高高极少使用除非跨库且规模小本地消息表是最容易落地的方案核心服务在本地库建一张消息表业务操作和消息写入在同一个数据库事务里完成保证二者同时成功或同时失败然后通过定时任务把消息表里的记录轮询发送到MQ。这个方案实现简单缺点是要写对账和清理逻辑消息表会持续膨胀需要定期归档。TCC思路是把一个操作拆成Try、Confirm、Cancel三步。下单时先Try冻结库存订单确认后Confirm扣减超时或失败则Cancel释放。TCC控制力强但实现时要处理空回滚和悬挂两个麻烦后面避坑章节详细写。SAGA适合跨多个服务的长时间业务流程比如下单、扣库存、出库、物流每一步一个事务失败则逆向补偿。选型经验是能不用分布式事务就不用了优先通过业务流程设计规避实在躲不开首选本地消息表资金类场景才上TCC链路特别长的流程用SAGA编排。4.2 幂等设计支付回调和下单请求为什么必须挡重分布式环境下重复请求是常态。支付平台回调可能因为网络超时重发三次用户手抖点了两下提交订单按钮MQ消费端处理完业务但还没来得及提交消息消息又被投递一次。如果没有幂等保护重复支付回调会让订单状态被覆盖成异常重复下单会造出两笔一模一样的订单。幂等设计最可靠的方式是数据库唯一约束。我一般会在核心服务里建一张幂等记录表业务执行前先插入记录成功才能继续。CREATE TABLE idempotent_record ( id bigint NOT NULL AUTO_INCREMENT, biz_type varchar(32) NOT NULL COMMENT 业务类型PAY_CALLBACK/ORDER_CREATE, biz_id varchar(64) NOT NULL COMMENT 业务唯一键例如支付流水号, status tinyint NOT NULL DEFAULT 0 COMMENT 0处理中 1成功 2失败, expire_time datetime NOT NULL COMMENT 幂等记录过期时间, PRIMARY KEY (id), UNIQUE KEY uk_biz_type_biz_id (biz_type, biz_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表的关键是uk_biz_type_biz_id联合唯一索引数据库层面保证同一个业务ID只能插入一次。处理流程是先插入status0的幂等记录插入成功说明是第一次请求继续执行业务插入失败说明是重复请求直接返回上次的结果。业务执行完把status改成1执行失败改成2下次重试就能根据状态决定是恢复还是跳过。幂等键的选取也讲究。支付回调要用支付平台返回的支付流水号因为一次支付可能对应多笔订单不对应该是一笔支付对应一个支付流水号用它当唯一键最准。下单请求要用客户端生成的UUID业务流水号而不是订单号——订单号是服务端创建的下单请求重复时订单号还没生成复用不了。设计文档里必须明确每个写接口的幂等键来源否则开发各写各的等于没做。4.3 秒杀与峰值流量缓存预减库存与MQ削峰秒杀是电商分布式架构里最极端的场景。有限的库存、巨大的瞬间流量系统目标不是让每个请求都成功而是保护主链路在极端流量下不崩溃。秒杀架构的核心是分层拦截请求先过网关限流再进秒杀服务判断资格然后从Redis预减库存最后通过消息队列异步落库。// 预扣库存Redis原子操作扣减后小于0说明已被抢完 Long remain redisTemplate.opsForValue().decrement(stockKey); if (remain 0) { redisTemplate.opsForValue().increment(stockKey); // 回补库存 return 已售罄; }这里decrement是Redis的原子操作不会出现两个并发请求同时读到同一个库存值从根源上防止超卖。但要注意扣减成功不等于真正买到Redis里扣的只是预占数量真正的库存扣减要等订单创建完成后再改数据库。如果用户抢到了却一直没有提交订单就要靠后面讲到的分布式定时任务把超时未支付的预占库存回补。秒杀流量不能全部放进订单创建链路。我一般会在秒杀服务后接一个消息队列用户抢到资格后立即返回排队中订单真正创建交给MQ消费者异步处理。这样就算瞬间有10万人抢真正打到订单服务的只有每秒钟几百个。数据库层还要加一道兜底扣库存的SQL必须带条件UPDATE t_inventory SET stock stock - 1 WHERE sku_id ? AND stock 0;AND stock 0这个条件保证即使Redis层被绕过或者缓存失效数据库也不会扣成负数。这条SQL是库存超卖的最后一道防线任何一层缓存失误数据库还能兜住。秒杀链路还必须有降级方案Redis挂了就把秒杀活动开关切到DB限流甚至直接拒绝本次秒杀而不是让异常流量打到订单核心链路。4.4 分布式定时任务Spring Cloud环境下为什么不能用Scheduled在Spring Cloud微服务架构里直接写Scheduled定时任务是新手最容易犯的错误。定时任务写在每个订单服务实例里服务部署了三台定时任务就同时执行三次。如果这个任务是关闭超时未支付订单三个实例同时跑可能关闭同一批订单配合幂等设计还不至于出大乱子但如果是对账后发送短信三次执行就是三条短信用户直接投诉。分布式定时任务的正解是把任务调度从业务实例里抽出来独立成调度中心由调度中心触发任务执行业务实例只提供执行入口。任务指定分片或者分布式锁保证任意时刻全局只有一个实例在执行某个任务。Aspect Component public class DistLockAspect { Around(annotation(distLock)) public Object around(ProceedingJoinPoint pjp, DistLock distLock) throws Throwable { String key lock: distLock.key(); // setIfAbsent 对应 SET key value NX EX原子占锁 boolean locked redisTemplate.opsForValue() .setIfAbsent(key, UUID.randomUUID().toString(), distLock.expireSeconds(), TimeUnit.SECONDS); if (!locked) { // 其他实例已经拿到锁直接跳过本次执行 return null; } try { return pjp.proceed(); } finally { // 释放锁时要先校验持有者防止误删别人的锁 redisTemplate.delete(key); } } }这段AOP的本质就是分布式锁。setIfAbsent用Redis的SET NX EX原子语义保证同一时刻只有一个实例能拿到锁expireSeconds是锁的过期时间必须大于任务实际执行耗时的最大值。否则任务执行到一半锁就过期了另一个实例又拿到锁任务重复执行这就是网上常说的锁过期导致的重复执行翻车。任务调度本身还要支持分片扫描。比如关闭超时订单这个任务每次扫描全表数据量大时一次扫描几十秒锁过期风险很高。更稳的写法是按用户ID分片每个分片扫一部分数据所有分片任务并行执行缩短单次执行时间。Cron表达式也建议错峰比如整点任务尽量避开数据库备份时间0 0/10 * * * ?这类每十分钟的任务最好配置在执行低峰期。5. 分布式架构设计避坑从真实故障里捞出来的五个翻车现场5.1 缓存穿透、击穿与雪崩现象一字之差处置完全不同现象某个商品详情接口突然大量请求打到数据库数据库QPS飙升到上限整条链路跟着慢。排查时有人喊缓存穿透有人喊缓存击穿最后发现根本不是一回事处置方案也完全不同。原因穿透是请求的key在缓存和数据库里都不存在比如恶意用不存在的商品ID刷接口缓存挡不住每个请求都穿透到数据库击穿是某个热点key在某一刻过期比如爆款商品的缓存刚好失效一瞬间大量请求全部涌向数据库雪崩则是大批key同时过期缓存层短时间内整体失效数据库和依赖服务被压垮。解决穿透要用空值缓存或布隆过滤器把不存在的key也缓存起来缓存时间设短一些比如60秒击穿要给热点key加互斥锁只放一个请求去重建缓存其他请求等待或直接返回旧值雪崩最容易预防缓存过期时间不要设置为固定值加一个随机偏移量让过期时间分散。比如商品缓存设置3600秒实际设置3600 random(0, 300)秒。这条经验几乎每次大促前都要检查一遍代码里写死过期时间的全部改成随机偏移。5.2 分库分表后的跨库查询与分布式ID现象订单分库分表之后运营后台的订单列表接口变得异常慢原来一次SQL查询变成几百次短查询页面加载几十秒都出不来。原因分片键选的是user_id但运营后台是按order_id或seller_id维度的查询路由规则定位不到具体分片只能全库全表扫描。再加上列表要展示买家昵称、商品标题而这些字段在订单表里没有冗余每一行都要回查用户服务和商品服务形成了典型的N1查询。解决按查询维度建索引表比如order_seller_index表按seller_id分片运营查询先定位到索引表再回订单表。更彻底的做法是建立独立的读模型订单写服务把数据同步到一套专门用于查询的分析库运营端完全走读模型不再碰在线订单表。如果沿用订单表直接做列表查询就得把买家昵称、商品标题等展示字段冗余到订单表里牺牲存储换性能。另外分布式ID要用雪花算法这类全局唯一方案不能继续用数据库自增主键否则多个分片会产生重复ID一旦生成又改不了后悔药都没有所以上线前就要定好。5.3 消息重复消费至少一次投递下的幂等底线现象用户支付成功后收到两条已支付通知账户积分被加了两次搜索索引里的订单状态被重复刷新。后端查日志发现支付成功消息被MQ投递了三次。原因消息中间件为了保证不丢消息普遍采用at-least-once投递语义消费者处理完消息还没来得及提交消费位点就宕机、超时或重启消息会被重新投递。这是消息系统的设计取舍不是配置错了。解决消费端必须按业务主键做幂等。以支付成功消息为例消费时先查幂等表里有没有这笔支付流水号的记录有就直接Ack没有才执行加积分、更新订单状态。幂等表唯一索引是兜底防止并发消息同时到达。我再强调一次不能依赖消息只被消费一次这种假设分布式环境里重复是常态幂等是底线。另外消费逻辑里的查询和插入要放在同一个事务里查询到不存在、然后插入时被另一个重复消息抢先插入这边就会插入失败所以要捕获唯一键冲突视为幂等成功。5.4 TCC空回滚与悬挂分布式事务的失控瞬间现象订单支付超时后TCC协调器发起了Cancel但此时Try请求因为网络原因还没到达账务服务。账务服务收到Cancel后查不到对应的Try记录就当作空操作直接返回成功。随后Try请求又到达了账务服务发现是新的Try正常执行了资源预留。结果是一笔已经取消的事务资源却被成功预占了。原因这是分布式事务里的空回滚和悬挂问题。空回滚是指在Try还没执行的情况下就收到Cancel做了无意义的回滚更危险的是悬挂Try在Cancel之后才到达资源被预留但永远不会被Confirm或Cancel释放。根因是网络调用时序无法保证Try、Confirm、Cancel三个请求通过不同的网络路径到达对端顺序可能颠倒。解决给每个事务分支加一张事务控制表记录事务ID和分支状态。Try到达时检查状态如果是CANCELED说明Cancel已经先到Try必须拒绝执行不做资源预留如果状态是NEW才允许执行Try并更新状态为TRIED。Cancel到达时如果查不到Try记录要写入一条CANCELED记录而不是直接返回这样才能挡住后到的Try。这张状态表是分布式事务参与者层面的记忆没有它TCC在一些异常时序下就是失控的。5.5 容量估算拍脑袋与压测失真现象压测报告显示核心下单链路能扛5000 QPS大促当天流量刚到3000 QPS数据库连接池先爆了订单服务频繁超时整个页面都在转圈。原因压测模型和真实流量不一致。压测时用脚本均匀地请求下单接口而真实用户的行为是点开页面、浏览、加购、下单混杂在一起的热点商品集中在少数几个SKU上Redis、数据库、消息队列的访问不是均匀的而是打在同一批热点数据上。还有压测只测了单业务链路没有模拟服务间调用互相挤占线程池的情况一个服务变慢线程堆积其他服务也跟着阻塞。解决压测场景要带长尾和热点。我的做法是按真实流量比例构造混合链路比如浏览、加购、下单、支付比例为100:10:1并且把数据集中到少量热点商品上压测时间至少持续1小时观察内存和连接数是否缓慢增长。容量估算要留30%冗余不要按压测峰值卡线部署。更重要的是做故障演练随机停掉一个订单服务实例确认网关能把流量切走确认数据库连接池不会被打满。压测结果不是用来写报告邀功的是拿来找系统最短板的。6. 验证与进阶用一个最小订单链路检验你的架构设计架构设计文档写得再完整不经过验证都是纸面功夫。我习惯在任何分布式架构落地前先搭一条最小订单链路来做验证用户服务、订单服务、库存服务、支付回调模拟、MQ、MySQL、Redis加上网关七个子系统就够了。不要一上来就铺三十个服务验证的是设计逻辑不是服务数量。验证项操作与关键参数预期结果单链路压测以预估峰值的1.5倍QPS混合链路压测1小时P99小于500ms成功率高于99.9%单点故障演练随机kill一个订单服务实例观察流量切换30秒内恢复失败率低于0.1%数据对账次日对账订单表、支付流水、库存流水差异为0不一致数据可定位缓存失效演练强制删除热点商品缓存观察DB负载DB峰值在可接受范围无击穿这四个验证做完架构设计里大部分纸上谈兵的问题都会现出原形。压测不过就回头调限流阈值、扩容节点不要硬上线对账有差异就查消息重试和幂等逻辑不要指望生产环境自愈。这些年带分布式架构我吃过最大的亏是上线前不做故障演练以为多部署几个实例就安全了结果一次机房网络抖动所有服务重试风暴直接打垮数据库。从那以后我的习惯是任何分布式方案上线前先问三个问题——某个服务挂了会怎样缓存全丢会怎样MQ整体不可用会怎样。把这三种情况在演练环境真实验证过再谈上线。这套方法不一定最先进但能让你在真正的故障来临时少一点手足无措。希望帮到你。本文还有配套的精品资源点击获取