电商下单库存分布式一致性完整方案文档

电商下单库存分布式一致性完整方案文档

文档说明

本文档为微服务分库架构(订单服务、库存服务独立DB)C端下单标准流程,大厂高并发通用方案,包含:数据表设计、完整下单链路、支付流程、取消/超时释放、故障自愈补偿、并发防超卖、各类异常兜底逻辑,无重型分布式事务(舍弃TCC/XA,采用先锁库存+异步最终一致性)。

一、整体架构概述

1. 微服务拆分

  1. 订单服务(order_db):创建订单、管理订单状态、支付超时延时消息、订单事件生产

  2. 库存服务(stock_db):库存冻结/解冻/真实扣减、冻结流水记录、延时补偿消息消费

  3. MQ中间件(RabbitMQ/RocketMQ)

    • 事件交换机:订单创建事件、订单取消事件、支付成功事件

    • 补偿队列:库存孤儿冻结延时补偿队列

  4. 通信方式:服务间RPC调用;服务内部事件MQ异步同步

    2. 核心设计思想

  5. 防超卖前置拦截:先冻结库存成功,再创建订单,杜绝大量无效订单;

  6. 数据一致性:不使用同步分布式事务,依靠业务流程自动释放+延时消息自愈兜底实现最终一致;

  7. 分层两套释放逻辑:正常业务释放(99.99%流量)+ 延时补偿自愈(极端宕机故障0.01%流量);

  8. 全程幂等设计,支持消息重复消费、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_stock

    2. 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 '库存冻结流水记录';
    
    

    三、完整业务全链路流程

    前置通用规则

  9. 所有库存更新使用原子UPDATE乐观锁SQL,单条SQL完成判断+修改,防并发超卖;

  10. 所有MQ消费、RPC接口均实现幂等,重复调用无数据错乱;

  11. 延时消息统一设置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个操作:

  1. 插入order_main订单记录,status=1(待支付),pay_expire_time=当前时间+15分钟;

  2. 插入local_message本地消息表,event_type=1(订单创建事件),send_status=0待发送;

  3. 事务提交。

    步骤3:异步发送订单创建事件

    订单服务定时任务轮询 local_message 待发送数据,投递MQ;投递成功更新send_status=1。

    库存服务监听「订单创建事件队列」(仅二次兜底,正常流程无业务逻辑),消费时再次执行冻结SQL,幂等防护网络超时漏锁。

    流程2:用户完成支付,真实扣减库存

  4. 支付渠道回调订单服务,订单服务更新订单主表 status=2(已支付);

  5. 订单服务写入local_message,事件类型=3支付成功;

  6. MQ推送支付成功事件至库存队列;

  7. 库存服务消费支付事件:

    • 根据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分钟支付超时延时消息触发。

  8. 订单服务更新 order_main status=3(已取消);

  9. 写入本地消息表,推送「订单取消事件」至库存MQ队列;

  10. 库存服务消费取消事件:

    • 查询对应冻结流水 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,产生孤儿冻结库存:

  11. stock_info locked_stock 增加占用;

  12. stock_lock_record 存在lock_status=1冻结流水;

  13. 无对应订单,不会触发支付超时、取消事件,库存永久锁定。

    延时补偿消息消费逻辑(5分钟自动执行)

  14. 获取消息内order_id,远程RPC调用订单服务查询订单;

  15. 分支1:查不到任何订单记录 → 判定孤儿冻结

    • 执行解冻SQL释放locked_stock;

    • 更新stock_lock_record lock_status=3已解冻;

  16. 分支2:查询到有效订单(待支付/已支付/已取消)

    • 无需任何操作,直接丢弃本条补偿消息;
  17. 执行完成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. 主链路仅1次RPC调用,接口耗时极低,支撑秒杀百万并发;

  2. 无分布式事务、无长事务锁,数据库压力小;

  3. 分层自愈,99.99%流量走正常业务释放,故障自动修复,人工无需介入;

  4. 避免TCC三段式RPC带来的性能损耗,架构简单易维护。

    缺点

    存在最多5分钟短暂数据不一致(孤儿库存临时占用可售额度),业务侧用户无感知。

    不适用场景

    资金强一致、大额贵重商品、内部ERP后台,此类场景选择Seata TCC分布式事务,牺牲并发换取实时强一致。

    七、核心SQL汇总

  5. 冻结库存原子乐观锁

    
    UPDATE stock_info
    
    SET locked_stock = locked_stock + #{num}
    
    WHERE sku_id = #{skuId}
    
    AND (total_stock - locked_stock - sale_stock) >= #{num};
    
    
  6. 支付成功真实扣减库存

    
    UPDATE stock_info
    
    SET locked_stock = locked_stock - #{num},
    
    sale_stock = sale_stock + #{num}
    
    WHERE sku_id = #{skuId};
    
    
  7. 订单取消/自愈解冻库存

    
    UPDATE stock_info
    
    SET locked_stock = locked_stock - #{num}
    
    WHERE sku_id = #{skuId};
    
    
  8. 定时巡检脏冻结流水(无全表扫描)

    
    SELECT * FROM stock_lock_record
    
    WHERE lock_status = 1 AND create_time < NOW() - INTERVAL 10 MINUTE;
    
    

    八、关键总结

  9. 分库微服务无法使用本地事务绑定「冻结库存+创建订单」,必然存在单边冻结故障;

  10. 行业标准方案:先锁库存,成功再建订单,从源头防止超卖;

  11. 两套释放逻辑分离:业务主动释放处理正常订单,延时补偿消息处理宕机孤儿库存;

  12. 依靠冻结流水表避免全表扫描,保证定时巡检性能;

  13. 全程乐观锁+幂等设计,并发、重复消息、乱序消息全部有防护;

  14. 短暂不一致可接受,换取超高并发吞吐,是淘宝、拼多多、京东C端下单通用落地架构。


本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。

萧兮的博客https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/019f1200-fa28-7c71-8f36-dd7b95da0ffd