电商下单库存分布式一致性完整方案文档
电商下单库存分布式一致性完整方案文档
文档说明
本文档为微服务分库架构(订单服务、库存服务独立DB)C端下单标准流程,大厂高并发通用方案,包含:数据表设计、完整下单链路、支付流程、取消/超时释放、故障自愈补偿、并发防超卖、各类异常兜底逻辑,无重型分布式事务(舍弃TCC/XA,采用先锁库存+异步最终一致性)。
一、整体架构概述
1. 微服务拆分
-
订单服务(order_db):创建订单、管理订单状态、支付超时延时消息、订单事件生产
-
库存服务(stock_db):库存冻结/解冻/真实扣减、冻结流水记录、延时补偿消息消费
-
MQ中间件(RabbitMQ/RocketMQ)
-
事件交换机:订单创建事件、订单取消事件、支付成功事件
-
补偿队列:库存孤儿冻结延时补偿队列
-
-
通信方式:服务间RPC调用;服务内部事件MQ异步同步
2. 核心设计思想
-
防超卖前置拦截:先冻结库存成功,再创建订单,杜绝大量无效订单;
-
数据一致性:不使用同步分布式事务,依靠业务流程自动释放+延时消息自愈兜底实现最终一致;
-
分层两套释放逻辑:正常业务释放(99.99%流量)+ 延时补偿自愈(极端宕机故障0.01%流量);
-
全程幂等设计,支持消息重复消费、RPC重复调用无脏数据。
二、全库数据表结构设计
库1:order_db 订单库(订单服务独有)
1. order_main 订单主表
CREATE TABLE `order_main` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键订单ID', `order_no` varchar(64) NOT NULL COMMENT '订单唯一编号', `user_id` bigint NOT NULL COMMENT '下单用户ID', `sku_id` bigint NOT NULL COMMENT '商品SKU', `buy_num` int NOT NULL COMMENT '购买数量', `order_amount` decimal(10,2) NOT NULL COMMENT '订单金额', `status` tinyint NOT NULL COMMENT '订单状态:1-已创建待支付 2-已支付 3-已取消 4-已完成', `pay_expire_time` datetime NOT NULL COMMENT '支付超时时间(默认下单后15分钟)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (`order_no`), KEY idx_user_id (`user_id`), KEY idx_status_expire (`status`,`pay_expire_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '订单主表';2. local_message 本地消息表(可靠消息投递保障)
解决:订单入库成功后,MQ消息发送宕机丢失问题
CREATE TABLE `local_message` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_id` bigint NOT NULL COMMENT '关联订单ID', `event_type` tinyint NOT NULL COMMENT '事件类型:1创建订单 2取消订单 3支付成功', `msg_content` text NOT NULL COMMENT '消息JSON体', `send_status` tinyint NOT NULL DEFAULT 0 COMMENT '0待发送 1发送成功 2发送失败待重试', `retry_count` int NOT NULL DEFAULT 0 COMMENT '重试次数', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_send_status_retry (`send_status`,`retry_count`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '订单本地消息事务表';库2:stock_db 库存库(库存服务独有)
1. stock_info 商品库存主表
CREATE TABLE `stock_info` ( `id` bigint NOT NULL AUTO_INCREMENT, `sku_id` bigint NOT NULL COMMENT '商品SKU唯一标识', `total_stock` int NOT NULL DEFAULT 0 COMMENT '商品总库存', `locked_stock` int NOT NULL DEFAULT 0 COMMENT '已冻结库存(下单锁定未支付)', `sale_stock` int NOT NULL DEFAULT 0 COMMENT '已真实售出库存', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_sku_id (`sku_id`), KEY idx_locked (`locked_stock`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '商品库存表';可售库存计算公式:
available_stock = total_stock - locked_stock - sale_stock2. stock_lock_record 库存冻结流水表(核心兜底依赖)
每条冻结操作必有一条流水,用于延时补偿查询、解冻标记,杜绝全表扫描
CREATE TABLE `stock_lock_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_id` bigint NOT NULL COMMENT '关联订单ID(关键,补偿查询依据)', `sku_id` bigint NOT NULL COMMENT '商品SKU', `lock_num` int NOT NULL COMMENT '冻结数量', `lock_status` tinyint NOT NULL COMMENT '1冻结中 2已支付扣减 3已解冻释放', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_order_sku (`order_id`,`sku_id`), KEY idx_lock_status_time (`lock_status`,`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '库存冻结流水记录';三、完整业务全链路流程
前置通用规则
-
所有库存更新使用原子UPDATE乐观锁SQL,单条SQL完成判断+修改,防并发超卖;
-
所有MQ消费、RPC接口均实现幂等,重复调用无数据错乱;
-
延时消息统一设置5分钟到期,给正常业务流程预留处理窗口。
流程1:用户发起下单(核心前置锁库存)
步骤1:订单服务接收下单请求,RPC调用库存服务冻结接口
库存服务开启独立本地事务,执行两步操作:
1)原子冻结库存SQL
UPDATE stock_info SET locked_stock = locked_stock + #{lockNum} WHERE sku_id = #{skuId} AND (total_stock - locked_stock - sale_stock) >= #{lockNum};
-
影响行数=1:库存充足,冻结成功;
-
影响行数=0:可售库存不足,事务回滚,返回下单失败。
2)冻结成功插入冻结流水记录
INSERT INTO stock_lock_record (order_id,sku_id,lock_num,lock_status) VALUES (#{orderId},#{skuId},#{lockNum},1);3)提交库存库完整事务,库存数据永久落库。
4)同步发送5分钟延时补偿消息到库存补偿队列,消息体携带 order_id、sku_id、lock_num。
作用:专门处理「库存冻结成功,订单创建失败」孤儿库存自愈。
5)RPC返回冻结成功标识给订单服务。
步骤2:订单服务创建订单(仅库存冻结成功才执行)
开启订单库本地事务,同时执行2个操作:
-
插入order_main订单记录,status=1(待支付),pay_expire_time=当前时间+15分钟;
-
插入local_message本地消息表,event_type=1(订单创建事件),send_status=0待发送;
-
事务提交。
步骤3:异步发送订单创建事件
订单服务定时任务轮询 local_message 待发送数据,投递MQ;投递成功更新send_status=1。
库存服务监听「订单创建事件队列」(仅二次兜底,正常流程无业务逻辑),消费时再次执行冻结SQL,幂等防护网络超时漏锁。
流程2:用户完成支付,真实扣减库存
-
支付渠道回调订单服务,订单服务更新订单主表 status=2(已支付);
-
订单服务写入local_message,事件类型=3支付成功;
-
MQ推送支付成功事件至库存队列;
-
库存服务消费支付事件:
-
根据order_id查询stock_lock_record冻结流水,校验lock_status=1冻结中;
-
原子SQL扣减冻结库存,转为真实售出:
UPDATE stock_info SET locked_stock = locked_stock - #{lockNum}, sale_stock = sale_stock + #{lockNum} WHERE sku_id = #{skuId}; -
更新冻结流水 lock_status=2(已扣减);
-
ack MQ消息,消费完成。
流程3:主动取消订单 / 支付超时自动取消(正常库存释放)
分两种触发场景:用户手动取消、15分钟支付超时延时消息触发。
-
-
订单服务更新 order_main status=3(已取消);
-
写入本地消息表,推送「订单取消事件」至库存MQ队列;
-
库存服务消费取消事件:
-
查询对应冻结流水 lock_status=1;
-
原子解冻SQL归还冻结额度:
UPDATE stock_info SET locked_stock = locked_stock - #{lockNum} WHERE sku_id = #{skuId}; -
更新stock_lock_record lock_status=3已解冻;
-
ack消息,流程结束。
四、故障自愈核心:5分钟延时补偿消息(处理单边脏数据)
触发场景
RPC库存冻结成功,返回途中订单服务宕机/数据库异常,订单未插入order_main,产生孤儿冻结库存:
-
-
stock_info locked_stock 增加占用;
-
stock_lock_record 存在lock_status=1冻结流水;
-
无对应订单,不会触发支付超时、取消事件,库存永久锁定。
延时补偿消息消费逻辑(5分钟自动执行)
-
获取消息内order_id,远程RPC调用订单服务查询订单;
-
分支1:查不到任何订单记录 → 判定孤儿冻结
-
执行解冻SQL释放locked_stock;
-
更新stock_lock_record lock_status=3已解冻;
-
-
分支2:查询到有效订单(待支付/已支付/已取消)
- 无需任何操作,直接丢弃本条补偿消息;
-
执行完成ack消息。
双层兜底(防止延时MQ消息丢失)
增加轻量定时巡检任务(不做全表扫描):
每10分钟执行,仅扫描超过10分钟未释放的冻结流水:
SELECT * FROM stock_lock_record WHERE lock_status = 1 AND create_time < NOW() - INTERVAL 10 MINUTE;遍历结果远程查订单,无订单统一解冻,作为MQ消息丢失的第二层保障。
五、各类异常场景完整复盘
异常1:并发抢购,库存不足
多个用户同时抢购最后1件商品,库存原子UPDATE乐观锁只有1行更新成功,其余返回0行,直接终止下单,不会生成无效订单,彻底杜绝超卖。
异常2:库存冻结成功,订单服务宕机无订单
现象:库存占用,无订单,无任何业务MQ消息;
自愈:5分钟延时补偿消息自动查询无订单,解冻归还库存,数据最终一致;
中间窗口:最多5分钟该部分库存不可售,无永久损耗。
异常3:订单创建成功,发送订单MQ消息宕机
依靠订单库local_message本地消息表,定时任务持续重试投递,保证库存最终收到订单创建事件做二次兜底。
异常4:MQ消息重复推送(网络重试)
所有消费逻辑基于stock_lock_record唯一索引 order_id+sku_id 幂等:
-
已扣减/已解冻流水再次消费,SQL无数据更新,无副作用;
异常5:取消订单消息先到达库存,订单创建消息延迟后到
库存消费取消消息时,远程查询订单状态为「待创建/待支付」,不是已取消,直接丢弃消息,等待后续创建消息到达正常冻结。
异常6:支付回调消息乱序,先收到支付、后收到订单创建
消费支付事件第一步远程校验订单状态,订单不存在直接丢弃,等待订单创建事件到达。
六、方案优缺点与选型说明
优势(大厂高并发首选)
-
主链路仅1次RPC调用,接口耗时极低,支撑秒杀百万并发;
-
无分布式事务、无长事务锁,数据库压力小;
-
分层自愈,99.99%流量走正常业务释放,故障自动修复,人工无需介入;
-
避免TCC三段式RPC带来的性能损耗,架构简单易维护。
缺点
存在最多5分钟短暂数据不一致(孤儿库存临时占用可售额度),业务侧用户无感知。
不适用场景
资金强一致、大额贵重商品、内部ERP后台,此类场景选择Seata TCC分布式事务,牺牲并发换取实时强一致。
七、核心SQL汇总
-
冻结库存原子乐观锁
UPDATE stock_info SET locked_stock = locked_stock + #{num} WHERE sku_id = #{skuId} AND (total_stock - locked_stock - sale_stock) >= #{num}; -
支付成功真实扣减库存
UPDATE stock_info SET locked_stock = locked_stock - #{num}, sale_stock = sale_stock + #{num} WHERE sku_id = #{skuId}; -
订单取消/自愈解冻库存
UPDATE stock_info SET locked_stock = locked_stock - #{num} WHERE sku_id = #{skuId}; -
定时巡检脏冻结流水(无全表扫描)
SELECT * FROM stock_lock_record WHERE lock_status = 1 AND create_time < NOW() - INTERVAL 10 MINUTE;八、关键总结
-
分库微服务无法使用本地事务绑定「冻结库存+创建订单」,必然存在单边冻结故障;
-
行业标准方案:先锁库存,成功再建订单,从源头防止超卖;
-
两套释放逻辑分离:业务主动释放处理正常订单,延时补偿消息处理宕机孤儿库存;
-
依靠冻结流水表避免全表扫描,保证定时巡检性能;
-
全程乐观锁+幂等设计,并发、重复消息、乱序消息全部有防护;
-
短暂不一致可接受,换取超高并发吞吐,是淘宝、拼多多、京东C端下单通用落地架构。
本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。
萧兮的博客:https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/019f1200-fa28-7c71-8f36-dd7b95da0ffd