简介:一份面向电商系统开发、运维及架构设计人员的PDF文档,系统讲解电商平台的整体软件架构。内容围绕中台服务、分布式缓存、消息队列、数据存储与访问接口、运维监控、第三方服务、安全机制及数据库读写分离等模块展开,并结合订单从提交、支付、库存扣减到物流签收的完整流程,帮助读者理解高并发场景下各组件如何协同。资源共1个PDF文件,压缩包大小342KB,内容精炼,适合快速建立电商架构知识框架。目前已有103人学习下载,对于初入电商领域的技术人员或需要梳理架构脉络的开发者,是一份可快速阅读的入门参考资料。
1. 电商平台软件架构:一张图读懂商业系统的骨架
做电商系统这些年,我拆过不少五花八门的系统,最后发现一个反直觉的规律:大促能不能扛住,技术选型只占一半,另一半看服务和数据的边界划得清不清楚。这份《电商平台软件架构》PDF 是一张完整的系统拓扑图,从用户端的中台服务、分布式缓存、消息队列,一路铺到中央主库、读写分离、OGG 同步和各类第三方接口,把一套电商平台该有的模块全部摊开了。适合两类人:一是刚接触电商后端、想搞明白服务之间怎么协作的新人;二是正在做架构梳理或系统重构、需要一份参考蓝图的开发者。下面按我自己的拆图习惯,从服务层、数据层、订单链路三个方向展开。
2. 中台服务层拆解:订单、库存、支付三大核心的职责与调用边界
2.1 为什么电商要抽中台:复用、隔离、独立伸缩
很多人一听到中台就觉得是过度设计,但电商这个场景不太一样。订单、支付、库存、商品这些能力,不是只有商城前端在用——客服系统要看订单、运营后台要改商品、App 和 Web 端要走同一套登录注册,如果每个端都自己实现一遍,光是需求变更就能把人拖死。中台的本质是把这些高频复用的业务能力下沉成独立服务,对外只暴露接口,对内各自管理数据和状态。
这份架构图里的中台服务层列得很典型:订单、图片、监控、同步、库存、产品、评论、积分/代金券/卡/余额、支付、购物车、活动促销、用户服务,外加分布式缓存和消息队列。我拆图时会先问一个问题:哪些服务直接参与交易主链路?答案是订单、库存、支付,这三个是命脉,促销和购物车是流量入口,评论和积分是增长辅助,监控和同步是运维底座。搞清楚这个主次关系,后面做容量规划和故障排查才有优先级。
中台带来的第二个好处是独立伸缩。平时订单量平稳,但大促时订单和支付的压力是日常的十倍以上,商品和评论可能只有两三倍。如果所有逻辑耦合在一个单体应用里,扩缩容只能整体操作,成本高还容易互相影响。拆成服务后,只需要对订单、支付、库存这几个热点服务做横向扩容,其他服务维持原样。
第三个好处是故障隔离。库存扣慢了不会拖垮支付,消息队列堆积了不会让用户登录超时。当然,代价是链路变长、数据一致性变复杂,这两点后面单独说。
2.2 核心服务时序:订单不直接扣库存,支付只认回调
先说订单和库存的边界。很多人第一版做电商会犯一个错:用户下单时,订单服务直接去数据库扣库存,然后生成订单。这样做在低并发下没问题,但一旦促销流量上来,订单服务和库存操作耦合在同一个事务里,数据库连接池很快就被打满。这套架构图的处理方式是:提交订单时先写入订单写库,同时通过消息队列通知库存服务扣减 Redis 中的预占库存,而不是在订单服务里同步操作库存表。
我自己的习惯是分成三步走。第一步,用户提交订单,订单服务校验商品状态和收货地址,生成待支付订单,写入订单写库;第二步,通过消息队列发送库存预占消息,库存服务在 Redis 中执行扣减,如果 Redis 库存不足,直接标记订单异常;第三步,支付回调到达后,订单服务把订单状态改为已支付,再触发库存实际扣减和订单同步。这里关键是"预占"和"实际扣减"分开,Redis 管高并发下的快速校验,数据库管最终一致。
支付服务的边界更明确:它只负责接收第三方支付平台的回调,并验证金额和订单号,然后更新订单支付状态。它不关心库存,也不关心物流。我在实际项目里见过把支付逻辑写进订单服务的做法,结果每次支付渠道升级,订单服务就要跟着发版,风险很大。正确姿势是支付服务单独部署,回调接口做成幂等的——同一个支付通知重复到达,最终效果只能算一次。
2.3 支撑型服务如何协同:商品、用户、评论、促销、购物车
商品服务在架构图里承担了商品静态化的职责。电商商品详情页流量极大,如果每次都去查数据库拼装详情,数据库压力不可控。常见做法是把商品详情渲染成静态 HTML 丢到 CDN,或者做页面片段缓存,只有价格、库存这类实时性要求高的字段走接口动态获取。商品服务负责维护商品基础数据和静态化任务的触发时机——商品信息变更时生成新的静态页,同步到文件系统和 CDN。
用户服务除了登录注册,还管理收藏和收货地址。注意架构图里把登录注册、收藏、收货地址归类在同一个服务下,这是合理的,因为这些数据都围绕用户这个聚合根,分开反而要跨服务频繁联表。评论服务带审核流:用户提交评论先进待审核,运营在中台管理后台审核通过后展示,评论写库和读库是分离的,读库通过同步服务从主库拉取。
促销服务比较特殊,它不直接参与交易,而是通过促销设置和审核来影响订单和商品的价格计算。订单生成时根据当前生效的促销活动计算优惠,这属于典型的读多写少场景,促销活动配置可以缓存到 Redis。购物车服务的亮点在架构图里写得很清楚:购物车所有信息直接写文件系统,而不是每次都走数据库。我一开始觉得这个设计怪,后来理解了——购物车的读写比极高、数据量又大,并且丢了也不影响核心交易,放文件系统加本地缓存是完全够用的。
服务之间通过分布式消息队列做异步通信。订单生成后发消息给库存服务、评论服务、积分服务,各取所需,互不阻塞。消息队列在这里起到削峰填谷的作用,大促时的瞬时流量先堆积在队列里,下游按自己的消费速度处理,不至于把数据库打垮。
3. 数据层与缓存:主库、读库、写库的读写路径与同步参数
3.1 这套数据库分库模型的读写路径
架构图里的数据库设计是典型的"多库分层"模型,我把它拆成三条链路来理解。
第一条是交易写链路。用户提交订单写入订单写库,支付回调更新订单状态也在写库完成,写库是交易数据的唯一入口。第二条是数据分发链路。写库的数据通过同步服务汇聚到中央主库,中央主库再向读库、TMS 库、WMS 库、BI 库分发。第三条是业务读链路。商城前端、客服系统、运营后台查订单列表和详情时,走的都是读库,不碰写库。
那 TMS、WMS、BI 这些库是干什么的?拆分一下:
| 库名 | 定位 | 典型数据 |
|---|---|---|
| 中央主库 | 全平台交易数据的汇聚点,是数据分发的源头 | 订单、用户、商品、库存全量 |
| 订单写库 | 订单主流程的写入入口,接收下单和支付更新 | 订单主表、订单明细 |
| 读库 | 面向查询场景的只读副本 | 订单宽表、商品快照 |
| TMS 库 | 物流运输系统 | 发货记录、签收记录 |
| WMS 库 | 仓储管理系统 | 拣货、出库、库存台账 |
| EDI 库 | 与外部系统做数据交换 | 订单同步给 WMS 的报文 |
| BI 库 | 数据分析与报表 | 订单明细、用户行为、销售汇总 |
读写分离和分库最大的收益不是性能,而是互不干扰。运营在后台拉大报表,SQL 再烂也只影响 BI 库;客服查订单历史,最多把读库压住,订单写库照常处理新订单。不过这套模型有个前提:同步链路必须稳。架构图里用的同步工具是 OGG,Oracle GoldenGate,属于日志级同步,性能很好,但配置和使用有不少讲究。
3.2 OGG 同步的参数配置与踩坑点
OGG 做的是数据库日志解析,源库把 redo log 里的变更解析成交易记录,投递到目标库重放。和直接写应用双写相比,OGG 对源库性能影响小,也不会侵入业务代码。我见过的电商项目里,订单写库到中央主库、中央主库到读库,这两条线基本都是 OGG 扛着的。
我在测试环境搭建 OGG 同步订单表时的配置大概长这样:
-- 抽取进程配置 GGSCI > EDIT PARAMS EXT_ORD EXTRACT EXT_ORD USERID ogg, PASSWORD ogg EXTTRAIL /u01/ogg/dirdat/er TABLE orders.t_order; TABLE orders.t_order_item; -- 投递进程配置 GGSCI > EDIT PARAMS DP_ORD EXTRACT DP_ORD RMTHOST 192.168.10.20, MGRPORT 7809 RMTTRAIL /u01/ogg/dirdat/er TABLE orders.t_order;抽取进程的核心参数有三个:EXTRACT指定进程名,EXTTRAIL是本地交易文件的落地路径,TABLE指定要同步的表。RMTHOST和MGRPORT是目标库的 Manager 进程地址和端口,RMTTRAIL是交易文件投递到目标机的路径。这里最容易踩的坑是表名不带用户名前缀,OGG 直接报找不到对象;另外一个坑是源库和目标库的字符集不一致,中文乱码。
OGG 同步也有一个天然的短板——延迟。日志解析和网络传输都有开销,高峰期五到十秒的延迟很正常。所以在订单流程里,同步属于最终一致:写库提交后,读库不会立刻看到这笔订单。架构图里"同步订单、从写库到中央主库再分发到读库"这条链路,本质上就是在最终一致的约束下,保证数据最终能对齐。
3.3 Redis 与 Memcache 的选型边界
架构图里同时出现了 Redis 和 Memcache 两组缓存服务,这是很多人的困惑点。我的理解是:Redis 负责需要数据结构支持的缓存场景,比如购物车用 Hash 结构存 SKU 和数量,库存用 String 结构做原子扣减,订单预占用 SETNX 做分布式锁。
Memcache 则更适合纯粹的 key-value 缓存,比如用户会话、验证码这类简单数据,读多写少,淘汰策略走 LRU。它的优势是多线程模型,在多核服务器上吞吐量不错,但对应地,Memcache 的数据结构太简单,没法做库存扣减这种需要原子操作的复杂逻辑。两者在同一套架构里共存并不冲突——混用缓存没有标准答案,按数据结构复杂度分流才是合理的。
我习惯给缓存数据统一设置过期时间,原则是:热点数据设置 30 到 60 分钟过期,实时性要求高的库存和价格设 30 秒到 1 分钟。缓存更新走主动失效加延迟双删,写操作先更新数据库,再删缓存,避免并发条件下缓存和数据库不一致。
4. 订单主链路实战:从提交到签收的状态流转与库存扣减
4.1 一条订单从提交到自动审单的完整分支
订单主链路是架构图里信息密度最高的部分,值得逐句拆解。用户提交订单后,第一步是写订单写库并检查写库是否正常,写失败直接返回下单失败,不进入后续流程。写成功后,生产订单并扣减 Redis 库存。接着判断是货到付款还是在线支付。
货到付款的分支相对简单:不需要等支付回调,直接进入中央主库的自动审单逻辑。在线支付则要等支付服务回调,如果长时间没收到回调,需要定时任务主动查支付平台状态。我的实践是:订单支付超时设为 30 分钟,超时未支付自动取消并释放 Redis 库存。
自动审单通过后,修改订单状态并同步到 EDI/WMS 库,WMS 系统抓取订单开始发货,发货后记录到 TMS 库。关键点在于:订单状态同步到 EDI 库→中央主库→读库→BK 记录,是严格按顺序执行的,前一段同步成功才触发下一段。这个设计是防消息乱序,因为订单状态是单向递增的,一旦顺序颠倒,读库看到的可能是一条已经签收但还没发货的订单。
订单签收后,流程回到同步服务,把签收状态同步到中央主库和读库,同时更新中央主库的库存信息。到这里,一条订单的生命周期才算真正走完。
4.2 库存扣减的并发控制与 Redis 原子操作
库存扣减是电商并发问题的重灾区。先看数据层实现,我用的是乐观锁加条件更新:
UPDATE t_stock SET available = available - #{num}, version = version + 1 WHERE sku_id = #{skuId} AND available >= #{num};available >= #{num}是防超卖的第一道关卡,数据库层面保证扣减数量不超过可用库存。version是乐观锁字段,并发更新时只有一个事务能成功,其他事务返回影响行数为 0,业务层捕获后提示用户库存不足。这套 SQL 的问题是数据库行锁压力大,高并发下更新排队明显,所以架构图里把 Redis 放在了数据库前面做预扣减。
Redis 侧用 Lua 脚本保证扣减的原子性:
local stock = tonumber(redis.call('GET', KEYS[1])) if stock and stock >= tonumber(ARGV[1]) then redis.call('DECRBY', KEYS[1], ARGV[1]) return 1 end return 0脚本的逻辑很直观:先读库存,库存足够才扣减,否则返回 0。因为整个脚本在 Redis 里是原子执行的,不会出现多个请求同时读取到相同库存的问题。实际使用时,我把KEYS[1]设为库存键名,ARGV[1]设为扣减数量,下单接口在发送消息队列之前先执行这个脚本,成功了才允许订单进入支付环节。
Redis 预扣减和数据库实际扣减怎么对齐?我的做法是:消息队列消费者收到订单支付成功的消息后,先执行上面的条件更新 SQL,成功则说明扣减无冲突;失败则回滚订单状态并联系统重新核对预占库存。分布式缓存和数据库之间的库存差异,用定时对账任务兜底,每 5 分钟做一次 Redis 与数据库的库存核对,差异超过阈值就告警。
4.3 多级同步的失败补偿与幂等设计
订单状态从写库到读库要经过三跳:写库→中央主库→读库。每一跳都可能失败。架构里的同步服务其实就是补偿机制的载体:同步服务记录每笔订单的同步状态,失败的任务进入重试队列,重试次数用尽后发告警给运维人工处理。
另一个必须处理的点是幂等。订单同步到 WMS,WMS 和电商系统之间网络抖动可能导致同一笔订单同步两次。解决办法是在 EDI 层做唯一键校验,用订单号加状态字段做联合唯一索引,重复插入直接跳过。支付回调接口同样要走幂等——同一个支付结果通知到达多次,第一次更新订单状态、第二次发现状态已变更则直接返回成功。
我在一个促销项目里因为没做幂等吃过亏:支付平台重试回调,订单状态被从"已支付"改回"待支付",用户付款成功却看到订单未支付,客诉直接爆掉。从那以后我坚持一条铁律:所有涉及状态流转的接口,先查状态再做更新,更新语句必须带当前状态条件。
5. 架构落地常见问题与排查:缓存、超卖、延迟与堆积
5.1 缓存穿透、击穿、雪崩:大促前的三座大山
现象:大促刚开始,数据库 CPU 突然飙到 99%,大量请求打到 DB,缓存形同虚设。线上表现为接口延迟从 50ms 涨到 3 秒,随后大面积超时。
原因:三种情况都可能导致。穿透是查询不存在的 key,请求绕开缓存直接砸向数据库;击穿是某个热点 key 刚好过期,一瞬间海量请求打到数据库;雪崩是大面积 key 在同一时段过期,数据库压力骤增。
解决:穿透用布隆过滤器拦截不存在的 key,或者在缓存里存空值并设置短过期时间。击穿的核心是热点 key 不过期加互斥重建——缓存里没有就先让一个线程去查库重建,其他线程自旋等待。雪崩在设置过期时间时加随机因子,比如基础 60 分钟加上 0 到 5 分钟的随机偏移,错开所有的 key 的过期时间。这套组合拳下来,大促前三件套基本能压住。
5.2 库存超卖:从 SQL 到 Lua 的两道闸门
现象:商品库存显示剩余 10 件,下单接口并发压测到 50 并发时,卖出去了 23 件。页面显示有货,付款后却下发缺货通知。
原因:典型的原因有两个。第一版代码先查库存再扣减,这两步之间是非原子操作,并发下多个请求读到同一库存值;另一个原因是没做数据库条件更新,扣减语句没有available >= #{num}的约束。
解决:数据库侧把扣减语句改成条件更新,影响行数为 0 就直接返回失败。Redis 侧用 Lua 脚本做原子预扣减,下单先过 Redis,扛住瞬时并发,再用数据库条件扣减保证最终一致性。验证方法是写个并发脚本,100 个线程同时抢 10 件库存,断言最终成功订单数必须等于 10。
5.3 订单状态同步延迟:先查 OGG 还是先查应用
现象:用户已支付订单,客服在后台查不到最新状态,延迟十几分钟才显示。技术排查时发现读库和写库数据不一致。
原因:同步链路有多个环节——OGG 抽取、网络投递、目标库重放。最常见的问题是 OGG 进程挂掉没人发现,或者源库归档日志空间满了导致抽取停滞。也有可能是同步服务处理消息失败后重试队列堆积。
解决:排查顺序很重要,先看 OGG 的 Manager 进程和 Extract 进程是否正常运行,用GGSCI > INFO ALL查看进程状态;再看 OGG 报告的延迟时间,确认是传输慢还是重放慢;最后才查应用层的同步服务,看消息队列有没有堆积、重试任务有没有卡住。我一般会搭一套同步延迟监控,延迟超过 60 秒就告警,避免等到客服投诉才发现。
5.4 消息队列堆积:定位消费瓶颈的办法
现象:大促中段,订单消息队列的积压量从几千涨到几十万,消费速度跟不上生产速度,下游库存和积分服务明显滞后。
原因:多数不是队列本身的问题,而是消费者依赖的资源出现了瓶颈。比如消费者线程池太小、数据库连接被占满、下游服务的接口变慢。把队列吞吐上不去简单归因于消费者数量不够,是常见的误判。
解决:先看每类消息的消费速率和生产速率,确认到底是哪个环节拖慢的。再定位到具体消费者实例的日志,看耗时集中在哪一步——是查数据库慢、调外部接口慢,还是反序列化出错一直重试。如果是外部接口变慢,做线程池隔离,避免一个慢接口拖死整个消费线程池;如果是数据库慢,看慢 SQL 和数据库连接池使用率。消息队列的堆积不是靠重启能解决的,问题基本都在下游。
6. 进阶:用一张体检清单反推这套架构是否够用
拿到架构图不要急着照抄,先做一轮自检。我每次给电商系统做架构评审,手里会拿一张固定的清单,逐项去套。
| 检查项 | 通过标准 | 不合格信号 |
|---|---|---|
| 服务边界 | 订单不直接操作库存表、支付不拼 SQL 改订单 | 跨服务联表查询、一个事务里操作两个服务的数据 |
| 缓存分层 | 热点数据有 Redis 兜底、会话类数据走 Memcache | 所有数据直查数据库,缓存形同虚设 |
| 同步链路 | 写库→主库→读库链路清晰,有延迟监控 | 读库数据滞后超过 60 秒无告警 |
| 库存一致性 | Redis 预扣与数据库扣减对齐,有对账任务 | 促销后库存对不上账,靠人工改数据 |
| 幂等保护 | 支付回调、订单同步、WMS 抓单均做幂等 | 同一通知重复处理导致状态回退 |
| 消息队列 | 生产消费速率有监控,堆积有告警 | 消费失败静默重试,看不到堆积指标 |
做完清单自检,再回到架构图本身去看一个核心问题:这套设计最怕什么?我认为是中央主库的单点风险。写库和读库都依赖中央主库做数据分发,一旦中央主库出问题,全链路的同步都会停摆。如果是生产环境,我会在主库上做主从高可用,或者引入备库承担分发职责。架构图没画这部分,但落地时不能省。
验证这套架构是否够用的另一个方法是做破坏性演练:把 OGG 进程手动停掉、把 Redis 主节点 kill 掉、往消息队列里灌十倍增量数据,观察系统行为是否符合预期。说白了,架构图是黑匣子,只有把故障提前注入进去,才知道哪些环节是纸糊的、哪些环节是真能扛的。有一次演练停了 OGG,读库数据滞后了半小时,但我们提前备好了手工触发同步的脚本,运营侧无感。从那以后我每次动同步链路,都强制走一遍备份校验和回滚演练,多花一小时在演练上,好过大促当天熬夜救火。希望这套拆解思路也能帮到你。
本文还有配套的精品资源,点击获取