
简介一份关于电商平台软件架构设计的PDF文档面向后端开发、系统架构师及电商技术学习者系统梳理了从用户端到中台服务的整体技术栈与关键设计要点。文档共1个PDF文件包体仅342KB内容紧凑目前已有103人学习。内容覆盖中台服务订单、库存、支付、购物车、积分/代金券、用户、评论、促销等、分布式缓存Redis、Memcache与消息队列RabbitMQ、Kafka、数据存储与访问接口、运维监控、安全防护及第三方对接并详解中央主库、读库、写库以及TMS、WMS、BI等库的数据库架构以及读写分离、Ogg同步等方案。订单流程部分完整呈现从提交订单、支付处理、库存扣减、自动审单、物流同步到订单签收的闭环逻辑配合黄金超市系统架构图可帮助读者直观理解高并发电商场景下各模块如何分工协作也可作为分布式系统设计时的模块划分与组件选型参考。1. 一份静态的 PDF为什么比一百次口头对齐更值钱做电商架构这些年我发现一个反直觉的现象很多团队的架构不是被设计垮的而是被「想当然」拖垮的。每个服务负责人心里都有一张架构图但画出来一比边界对不上依赖方向也不一致。「电商平台软件架构.pdf」这个标题的价值恰恰在于把散落在聊天记录、口头承诺和各自脑中的架构决策收敛成一份团队真正能照着施工、对着排查、按着演进的基准文件。它不是写给评审看的汇报件而是开发、测试、运维之间对齐的地图。适合谁读准备从单体往微服务拆的团队、刚接手老系统的技术负责人、以及想系统梳理电商核心域的新架构师。下面我从业务架构拆起一路讲到技术选型、数据一致性和落地避坑最后落到文档怎么维护才能不腐化。2. 电商平台的业务架构先厘清领域边界再谈技术选型很多团队一上来就聊 Spring Cloud 还是 Dubbo、Kafka 还是 RocketMQ结果服务拆到一半发现边界是糊涂的订单服务里塞了库存扣减支付回调直接改订单表营销活动把优惠金额算在订单服务里。技术选型再先进也救不了混乱的职责划分。所以一份合格的电商软件架构文档第一部分必须是业务架构而不是技术架构。只有把「哪些事归谁管」讲清楚后面的微服务边界、数据归属、消息路由才有依据。2.1 电商业务域的划分逻辑按业务能力而不是按页面拆常见的电商平台可以拆成七个核心业务域商品域管类目、SPU、SKU 和上下架库存域管实物库存和锁库存交易域管购物车、订单、结算支付域管支付单、渠道对接和退款营销域管优惠券、满减、秒杀活动履约域管发货、物流状态会员域管账号、地址、积分。划分原则只有一个一个业务能力归一个域域与域之间通过接口协作禁止跨域直接修改对方的表。这个分层思路和嵌入式系统里的分层软件架构是一个道理——上层依赖下层同层之间不互相调。放到电商里就是交易域可以调库存域的接口但库存域绝不能反过来读订单表。这里多说一句现在不少团队在做跨境电商多平台订单抓取订单来源从自营 App 扩展到多个第三方平台这类需求考验的正是订单域对「多来源订单」的建模能力。架构上提前留好 source、channel、platform_order_no 字段比事后打补丁省事得多。2.2 下单主链路走一遍模块之间如何协作拿一笔最普通的下单流程来看业务域怎么协作用户在商品域查 SKU 信息和价格提交订单时交易域创建订单然后依次调用库存域锁库存、营销域计算优惠、支付域创建支付单。支付回调到达后支付域通知交易域支付成功交易域再通知履约域生成发货单。这条链路有个关键约束依赖方向始终是从交易域指向其他域不允许反向调用。为什么因为订单是电商的主轴反向依赖会让所有域都耦合到订单表上架构立刻失去弹性。这份依赖关系应该画成一张组件图放进 PDF 里图里每个箭头都要能对应到代码里的一处接口调用。如果画图时发现箭头绕成了环那说明边界没划干净先别急着写代码。2.3 订单状态机用代码把状态流转钉死订单是最典型的「状态敏感」业务对象。从创建到支付、发货、完成、关闭每一步都要有明确的前置条件绝不能允许任何服务绕过校验直接改状态字段。我一般会把状态机收敛在订单域内部用枚举加事件驱动来实现核心逻辑不复杂public enum OrderState { INIT, // 已创建待支付 PENDING_PAYMENT, // 待支付 PAID, // 已支付待发货 FULFILLING, // 履约中已发货 COMPLETED, // 已完成 CANCELED; // 已关闭 public OrderState transition(OrderEvent event) { switch (this) { case INIT: case PENDING_PAYMENT: if (event OrderEvent.PAY_SUCCESS) return PAID; if (event OrderEvent.USER_CANCEL) return CANCELED; break; case PAID: if (event OrderEvent.SHIP) return FULFILLING; break; case FULFILLING: if (event OrderEvent.CONFIRM_RECEIPT) return COMPLETED; break; default: break; } throw new IllegalStateException(非法订单状态流转: this event); } }逻辑说明所有状态变更必须经过transition方法传入当前状态和业务事件返回新状态非法的流转直接抛异常避免静默失败。参数说明PAY_SUCCESS只能由支付回调触发USER_CANCEL只能由用户主动发起超时未支付单需要由定时任务构造一个TIMEOUT_CANCEL事件走同一个方法而不是在定时任务里直接改库。把这段代码放进架构文档比描述十遍「订单状态要规范」都管用。2.4 架构文档怎么组织从 C4 的四个层次落到 PDF 章节业务架构部分建议直接用 C4 模型的思路来组织内容刚好对应 PDF 的四个章节。第一层 Context 是系统上下文图画清楚电商平台和用户、支付渠道、物流系统、第三方电商平台之间的外部关系给产品经理和老板看。第二层 Container 是应用容器图画出每个业务域对应的服务、数据库、消息队列给开发和运维看。第三层 Component 是组件依赖图展开每个服务内部的模块和调用关系。第四层 Code 是代码级说明放订单状态机、库存扣减逻辑、接口定义这类关键代码片段。分好层之后不同角色拿到 PDF 都能快速找到自己关心的那一层不会对着同一张密密麻麻的架构图干瞪眼。3. 技术架构与中间件选型微服务、注册中心、MQ 与缓存的参数怎么定业务域定清楚之后技术架构才有锚点。这一章讲的是电商平台软件架构里最常被问到的中间件选型问题。我的判断标准很朴素不追最新的只追团队能维护的。软件架构模式里有微服务、事件驱动、分层架构等几种主流模式电商平台基本是微服务 事件驱动的组合同步 RPC 走核心交易链路异步消息做解耦和最终一致性。3.1 服务拆分粒度先按域拆再按团队边界验证服务拆分没有标准答案但我有个经验法则先完全按照业务域拆拆完再看团队的 ownership 是否清晰。订单服务、商品服务、库存服务、支付服务、营销服务、履约服务这是电商最经典的拆分结果。每个服务独立部署、独立数据库、独立发布。拆分时机也很重要团队超过十个人、发布经常互相踩、单库连接数接近上限这三个信号出现任意两个就该动手拆了。常见的误用是继续往下拆比如把订单服务拆成订单创建服务和订单查询服务理由是读写量不一样。这种拆法会导致订单域内部调用泛滥事务边界被撕碎得不偿失。3.2 注册中心与配置中心Nacos 的 CAP 取舍与关键配置服务拆完第一件事是解决「服务之间怎么找到彼此」。电商场景里我用得最多的是 Nacos原因是它同时承担注册中心和配置中心两个角色省一套运维。注册中心选型要清醒服务发现场景下 AP 比 CP 重要网络分区时宁可拿到稍旧的服务列表也不能让整个调用链阻塞所以 ZooKeeper 那种 CP 语义的服务发现并不适合高并发电商场景。Nacos 的核心配置大致长这样spring: cloud: nacos: discovery: server-addr: nacos.internal:8848 namespace: prod-order group: E-COMMERCE register-enabled: true config: server-addr: nacos.internal:8848 namespace: prod-order file-extension: yaml shared-configs: ->local stock redis.call(HGET, KEYS[1], available) if tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(HINCRBY, KEYS[1], available, -ARGV[1]) redis.call(HINCRBY, KEYS[1], locked, ARGV[1]) return 1逻辑说明脚本先读取available可用库存数量不足直接返回 0 表示抢购失败数量充足就把可用库存减掉、锁定库存加上返回 1 表示扣减成功。KEYS[1]是sku:stock:{skuId}这个 Hash 的 keyARGV[1]是本次扣减数量。这段逻辑放进 Lua 脚本里整体执行Redis 保证原子性不需要额外加分布式锁。参数说明locked 字段表示已锁定未支付的库存订单超时未支付时要释放锁定库存释放也是HINCRBY available N和HINCRBY locked -N两个命令打包进 Lua。数据库侧兜底一句 SQL 就够UPDATE stock SET available available - #{num} WHERE sku_id #{skuId} AND available #{num}确保数据库账实相符。这里有个血泪经验千万不要在代码里写成「先查库存判断足够再扣减」三步之间必有并发窗口压测一到必现超卖。4.3 分布式事务的取舍本地消息表是电商最常见的落法下单要扣库存、扣优惠券、创建支付单跨服务之后本地事务管不住了。业界方案有 XA 两阶段提交、TCC、SAGA、本地消息表加消息队列电商场景我的排序是本地消息表最稳TCC 其次XA 基本不碰。对比很清楚方案一致性吞吐侵入性适用XA / 2PC强低高银行核心账务互联网不适用TCC最终一致中高需要准实时回滚的资金操作SAGA最终一致中高中长流程编排本地消息表 MQ最终一致高中电商订单、库存、积分XA 的问题在于长事务持有数据库锁高并发下会把整个系统拖死。本地消息表的核心思路是做交易的服务在本地数据库里写业务数据和消息数据同一个本地事务一起提交然后由后台任务把消息发给 MQ消费方处理成功后回调确认。消息表建表语句参考CREATE TABLE local_message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(32) NOT NULL COMMENT 业务类型: order/payment/stock, biz_id VARCHAR(64) NOT NULL COMMENT 业务单号, payload TEXT NOT NULL COMMENT 消息体JSON, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待发送 1已发送 2消费确认 3死信, retry_count INT NOT NULL DEFAULT 0, next_retry_time DATETIME NOT NULL, created_at DATETIME NOT NULL, UNIQUE KEY uk_biz_type_biz (biz_type, biz_id) ) COMMENT 本地消息表;逻辑说明业务和消息在同一个本地事务里写入要么都成功要么都失败不会出现「订单建了但消息没发出去」的中间态。status字段记录消息生命周期后台任务定时扫描待发送消息按next_retry_time顺序重投。参数说明uk_biz_type_biz唯一索引保证同一业务只产生一条消息这是幂等的第一道闸门重试间隔用指数退避比如 1 分钟、5 分钟、30 分钟超过 5 次进死信人工介入排查。这套方案看起来土但它是互联网公司验证了十几年的可靠路径。4.4 对账支付对账与库存对账的兜底最终一致的系统必须有对账机制兜底否则账实不符只能靠运气发现。支付对账的做法是接收支付回调之后不要直接信任回调结果而是用一个定时任务每天从支付渠道拉取账单和本地支付单逐笔比对金额不一致或状态不一致的进入对账差异表由财务人工处理。库存对账也是同理每晚跑一次盘点任务把 Redis 里的库存和数据库库存比对差异超过阈值就告警。对账不是可选项是电商架构的必要组件架构文档里必须给对账模块留一个专门的章节。5. 电商架构落地避坑5 个高频问题与排查路径下面这些坑基本每个电商团队都会踩到至少两三个。写在这里省得拿生产环境试错。每一条我都按「现象、原因、解决」的顺序讲清楚排查的时候照着这个思路走能少走很多弯路。5.1 大促压测一上来订单就超卖现象压测刚开始两分钟订单量就超过了库存总量数据库里库存变成负数。原因代码里用了「先查库存再判断最后扣减」的三步逻辑查询和扣减之间有并发窗口多个请求同时读到剩余 1 件全部判断「足够」一起扣成负数另外缓存和数据库的库存双写时序不一致缓存还有货但数据库已经扣完。解决扣减统一收敛到库存服务Redis 加 Lua 原子扣减负责抗流量数据库UPDATE ... WHERE available #{num}做最终兜底两边任何一个失败都返回抢购失败。压测时盯的指标也要换不要只看 TPS要重点看扣减接口的失败率和库存的最终一致性校验。5.2 支付回调重复处理订单状态被改乱现象同一笔支付成功回调被消息队列重试了三次订单先从「待支付」变成「已支付」又被后到的重复消息改回「待支付」甚至生成了两笔履约单。原因消息重试在分布式系统里不是异常而是常态消费端没有做幂等同时订单状态没有被状态机保护允许从「已支付」回退到「待支付」。解决消费端用paymentNo orderId做去重表唯一索引兜底订单状态流转必须走第 2.3 节的状态机先校验当前状态是否允许该次流转非法流转直接抛异常。去重表里的数据保留 30 天再归档因为支付渠道的延迟回调最长可能超过一周。5.3 架构图与真实代码脱节文档成了展厅展品现象架构文档画着「订单服务包含支付和库存能力」代码里早就拆成了三个独立服务线上排查问题时没人愿意信文档。原因架构文档靠人工维护没有自动化校验手段拆分服务时的架构决策没有记录后来的人不知道当初为什么这么拆遇到问题就往回改。解决给关键依赖方向加架构守护测试用 ArchUnit 这类工具把「订单服务不得依赖履约服务」之类的约束写成单测跑在 CI 里代码违反约束直接构建失败。架构文档不要求实时覆盖每个接口细节但「服务清单」和「依赖方向」这两块必须和代码一致不一致时以代码为准、更新文档。5.4 PDF 架构图放大发虚、关键字搜不到现象把画图工具导出的 PNG 位图直接贴进 PDF用大屏投映时放大发虚在内部文档系统里想搜索一个服务名PDF 完全搜不到。原因位图在缩放时会失真矢量图不会图片型 PDF 没有文字层全文检索无法命中。解决画图工具导出 SVG 或 PDF 矢量格式再嵌进文档文字说明直接排版成文字层而不是截图如果原始资料是扫描件或图片型 PDF先跑一遍 OCR 识别再加一层隐藏文字这就是常见的 PDF 解析处理流程。公司内部如果用的是 kkfileview 这类在线预览服务还要确认版本的兼容性单份 PDF 控制在 20MB 以内输出为 PDF/A 格式方便长期存档。5.5 新同学按文档搭环境三天没起来现象文档上写着「安装 Nacos」没说版本号配置片段缺了两个参数中间件端口和本地已有服务冲突新同学卡在环境搭建上好几天。原因架构文档里只写了「架构图」没写「运行参数」默认读者和作者掌握同样的信息。解决在 PDF 末尾固定附一张环境依赖清单列清组件、版本、端口、核心配置项和启动命令。版本号要精确到小版本比如 Nacos 2.2.3而不是写「Nacos 2.x」。这张表要写进架构评审的 checklist 里评审时一项一项核对缺了就驳回。6. 让架构文档活起来ADR、架构守护与 PDF 的可持续维护架构文档最怕写完就死。我给团队定的规矩是改架构先写 ADR再改文档最后改代码。ADR 就是架构决策记录一个决策一个文件模板固定四段背景、决策、权衡、影响。比如「订单表分片」这条决策背景是单库连接数告警决策是按买家 ID 分 1024 个分片权衡是放弃订单 ID 路由支持跨分片查询影响是下游所有服务必须带分片键调用。有了这条记录三个月后没人会问「当初为什么这么拆」。依赖方向的守护靠测试来钉。把架构文档里的依赖约束写成 ArchUnit 断言挂在 CI 流水线上违反就红ArchTest static final ArchRule order_should_not_depend_on_fulfillment noClasses() .that().resideInAPackage(..order..) .should().dependOnClassesThat() .resideInAPackage(..fulfillment..);逻辑说明这条断言检查order包下所有类禁止依赖fulfillment包下的任何类违反即测试失败。参数说明包名前缀必须和架构文档里的服务名严格对应否则守护的就是一套对不上的名字。文档源文件我用 Markdown 管理代码仓库和代码放一起发版时构建导出 PDFPDF 编辑器只用来临时批注不直接改正式内容。想改版就改 Markdown 重新导出这也是为什么我从不建议大家拿 PDF 转 Word 来改架构文档——转出来的格式一团糟还不如重新排。这份文档从「展厅展品」变成「施工图」靠的不是画得多漂亮是每个决策都记了账、每个约束都能被机器检查。我自己第一次带架构时吃过文档脱节的亏后来把架构守护挂上 CI 才彻底解决。希望帮到你。本文还有配套的精品资源点击获取