如何设计一个分布式订单系统

4 阅读14分钟

分布式订单系统,重点需要放在“高并发下单、分布式一致性、库存/支付/履约协同、可扩展性”这几块.

本篇文章结合电商场景来设计一个比较完整的分布式订单系统


分布式订单系统设计

一、什么是分布式订单系统

分布式订单系统,本质上就是把传统单体里的“下单、库存、支付、优惠、物流、售后”等能力拆成多个独立服务,通过分布式架构来支撑高并发、高可用和高扩展。

在电商场景里,用户点一次“提交订单”,背后并不是一个简单的 insert,而是会串起很多动作:

  • 校验商品是否可售
  • 校验库存是否充足
  • 计算价格、优惠、运费
  • 锁定库存
  • 生成订单
  • 发起支付
  • 支付成功后推进履约
  • 超时未支付自动取消
  • 取消后释放库存

所以,订单系统其实是整个交易中台的核心枢纽。


二、系统目标

设计分布式订单系统时,核心目标一般有这几个:

1. 高可用

订单是核心链路,不能轻易挂。即使某个非核心服务异常,也不能影响用户最基本的下单。

2. 高并发

大促、秒杀、直播带货时,下单流量会瞬时暴涨,系统要能扛住。

3. 数据一致性

订单、库存、支付状态必须尽可能一致,至少要保证最终一致性

4. 可扩展

订单量越来越大后,必须支持分库分表、服务横向扩容、异步解耦。

5. 可追踪

订单生命周期长,必须能清晰看到每一步状态变化,方便排查问题。


三、典型业务场景

以一个电商平台为例:

用户购买一台手机,原价 3999,使用了 200 元优惠券,支付方式是支付宝。
整个订单链路可能是:

  1. 用户进入确认订单页
  2. 系统加载商品、地址、优惠、运费信息
  3. 用户点击提交订单
  4. 订单系统校验参数、价格、库存
  5. 库存系统锁库存
  6. 订单系统创建订单
  7. 支付系统生成支付单
  8. 用户完成支付
  9. 支付回调成功,订单状态改为“已支付”
  10. 仓储系统收到消息,开始发货
  11. 物流回传运单号
  12. 用户收货后订单完成

这条链路就是典型的分布式订单流程。


四、整体架构设计

可以把系统拆成下面几个核心服务:

1. 网关层

  • API Gateway / Nginx
  • 做统一鉴权、限流、路由、灰度、日志埋点

2. 接入层

  • App、H5、PC、小程序
  • 调用交易聚合服务或订单服务

3. 核心业务服务层

  • 订单服务:订单创建、查询、状态流转、取消、关闭
  • 购物车服务:维护用户待下单商品
  • 商品服务:商品基本信息、上下架状态
  • 价格服务:价格计算、优惠计算、促销规则
  • 库存服务:库存扣减、锁定、释放
  • 支付服务:支付单创建、支付状态同步
  • 优惠券服务:优惠券校验、核销、回滚
  • 履约/仓储服务:发货、出库、物流
  • 用户服务:地址、会员等级、风控信息
  • 售后服务:退款、退货、取消售后

4. 基础设施层

  • MySQL / OceanBase:订单持久化
  • Redis:缓存、幂等、分布式锁、热点数据
  • RocketMQ / Kafka:异步解耦、最终一致性
  • Elasticsearch:订单搜索
  • XXL-JOB / Quartz:超时关单、补偿任务
  • ShardingSphere:分库分表
  • Prometheus + Grafana:监控告警
  • SkyWalking / Zipkin:链路追踪

五、订单核心表设计

订单系统表设计一般会拆成几张主表,而不是一张大宽表。

1. 订单主表 order_info

核心字段通常有:

  • order_id:订单号
  • user_id:用户ID
  • order_status:订单状态
  • total_amount:订单总金额
  • pay_amount:实付金额
  • coupon_amount:优惠金额
  • freight_amount:运费
  • pay_status:支付状态
  • source:订单来源(App/H5/小程序)
  • create_time / update_time

2. 订单明细表 order_item

一笔订单可能有多个商品明细:

  • id
  • order_id
  • sku_id
  • spu_id
  • sku_name
  • buy_num
  • sale_price
  • item_amount

3. 订单地址表 order_address

保存收货信息快照,避免后续用户改地址影响历史订单。

4. 订单操作流水表 order_operate_log

记录状态流转:

  • 订单创建
  • 支付成功
  • 发货
  • 取消
  • 完成
  • 退款

5. 支付单表 pay_order

订单和支付往往分开存储,方便支持多支付渠道。

6. 库存锁定表 stock_reservation

记录这笔订单锁了哪些库存,便于超时释放和异常补偿。


六、订单状态机设计

分布式订单系统一定要有清晰的状态机。

例如:

  • INIT:初始化
  • PENDING_PAYMENT:待支付
  • PAID:已支付
  • FULFILLING:履约中
  • SHIPPED:已发货
  • COMPLETED:已完成
  • CANCELLED:已取消
  • CLOSED:已关闭
  • REFUNDING:退款中
  • REFUNDED:已退款

支付状态、履约状态、售后状态最好拆开,不要全部塞进一个字段里,否则后面很难维护。

比如:

  • 订单状态:待支付、已完成、已关闭
  • 支付状态:未支付、支付中、已支付、已退款
  • 发货状态:未发货、部分发货、已发货

这样更清晰。


七、核心下单流程设计

1. 确认订单页

用户点击“去结算”后,系统一般不会直接落库,而是先查:

  • 购物车商品
  • 实时价格
  • 可用优惠券
  • 收货地址
  • 运费模板
  • 库存是否可售

这一步更多是展示层聚合


2. 提交订单

用户真正点“提交订单”时,核心流程一般是:

第一步:参数校验

  • 用户是否合法
  • 商品是否存在
  • SKU 是否可售
  • 收货地址是否有效
  • 数量是否合法
  • 是否重复提交

第二步:防重与幂等

通常要做两层:

  • 前端防重复点击
  • 服务端幂等控制

常见做法:

  • 提交订单前生成 orderToken
  • Redis 保存 token
  • 提交时校验并删除 token
  • 删除成功才允许继续

这样可以防止用户连续点两次提交。

第三步:价格校验

不能信前端价格,必须服务端重算:

  • 商品总价
  • 促销优惠
  • 优惠券抵扣
  • 运费
  • 实付金额

否则会被篡改。

第四步:锁库存

一般不建议直接扣减真实库存,而是先锁库存。

例如库存服务提供接口:

  • lockStock(orderId, skuId, num)
  • releaseStock(orderId)
  • deductStock(orderId) 或支付成功后确认扣减

锁库存成功后,说明这笔订单有资格继续创建。

第五步:创建订单

订单服务落库:

  • 订单主表
  • 订单明细表
  • 地址快照
  • 订单日志

状态设为:待支付

第六步:发送下单成功消息

发送 MQ 消息给下游:

  • 优惠券服务:冻结/核销优惠券
  • 支付服务:生成支付单
  • 风控系统:记录交易行为
  • 营销系统:统计活动参与

第七步:返回下单结果

返回:

  • 订单号
  • 支付金额
  • 支付链接或支付参数

八、库存与订单如何保证一致性

这是面试里最常问的点。

分布式系统里,订单和库存通常是两个独立服务、两套数据库,不可能靠本地事务一次性完成,所以通常采用最终一致性方案。


九、常见一致性方案

方案一:本地事务 + MQ 最终一致性

这是最常见、最实用的方案。

下单时

  1. 调库存服务锁库存
  2. 订单服务本地事务创建订单
  3. 本地事务成功后发送 MQ 消息

支付成功时

  1. 支付系统发送“支付成功”消息
  2. 订单服务更新为已支付
  3. 库存服务收到消息后正式扣减库存
  4. 履约系统收到消息后开始发货

超时取消时

  1. 定时任务扫描待支付超时订单
  2. 订单状态改为已取消
  3. 发送取消消息
  4. 库存服务释放锁定库存
  5. 优惠券服务回滚优惠券状态

这个方案的优点是:

  • 性能高
  • 解耦好
  • 适合高并发

缺点是:

  • 会有短暂不一致
  • 需要补偿机制

方案二:TCC

适合资金、库存这类强业务约束场景。

比如下单:

  • Try:尝试锁库存、预留资源
  • Confirm:支付成功后确认扣减
  • Cancel:失败或超时后释放

优点:

  • 业务语义清晰
  • 一致性更强

缺点:

  • 侵入性高
  • 开发复杂
  • 对每个服务要求高

电商大部分订单主链路里,通常不会全链路都上 TCC,只会在关键链路局部使用。


方案三:Seata AT/XA

理论上可行,但在高并发交易系统里一般比较谨慎。

原因是:

  • 性能损耗较大
  • 长事务影响吞吐
  • 对数据库和 SQL 有要求
  • 容易成为瓶颈

所以电商订单系统更常见的还是 柔性事务 + 最终一致性


十、如何防止超卖

这是订单系统和库存系统的结合点。

常见方案:

1. Redis 预扣减 + DB 最终落库

例如某商品库存 100:

  • Redis 先放 100
  • 请求到来先原子扣减 Redis
  • 扣减成功再进入后续流程
  • 最终异步同步到数据库

优点:快,适合秒杀
缺点:实现复杂,补偿要做好

2. 数据库乐观锁

SQL 类似:

update stock
set available = available - 1
where sku_id = ? and available >= 1 and version = ?

优点:简单
缺点:高并发下冲突严重

3. 库存分段/分桶

把库存拆成多个分片,降低热点竞争。

4. 队列削峰

用户请求先进入 MQ,消费者串行或有限并发处理某个商品的库存扣减。


十一、支付系统怎么衔接

订单系统与支付系统通常要解耦。

下单后

订单服务创建订单,状态为待支付,然后调用支付服务生成支付单。

支付成功后

支付平台回调支付服务,支付服务再做两件事:

  1. 更新支付单状态
  2. 发送“支付成功”消息给订单服务

订单服务消费消息后:

  • 校验金额
  • 校验订单状态
  • 更新订单为已支付
  • 记录支付流水
  • 通知履约发货

这里一定要注意:

1. 支付回调幂等

支付平台可能多次通知,必须保证多次处理结果一样。

常见做法:

  • 按支付流水号做唯一约束
  • 按订单状态机控制重复更新

2. 金额校验

支付金额必须和订单应付金额一致。

3. 乱序处理

可能出现支付成功消息先到、订单状态稍后更新的情况,要有重试和补偿。


十二、订单号如何生成

分布式订单系统不能用数据库自增 ID 作为对外订单号。

常见方案:

1. 雪花算法 Snowflake

特点:

  • 全局唯一
  • 趋势递增
  • 性能高

适合做内部主键 ID。

2. 业务订单号

一般会拼接:

  • 日期
  • 业务标识
  • 用户尾号/机器号
  • 随机数/序列号

例如:

OD202603261234560001

这样更适合展示给用户和客服。


十三、如何做幂等设计

订单系统里幂等非常重要。

1. 提交订单幂等

防止用户重复点提交:

  • token 机制
  • Redis setnx
  • 请求号去重

2. 支付回调幂等

防止重复通知:

  • 唯一流水号
  • 状态机控制

3. MQ 消费幂等

防止消息重复消费:

  • 业务去重表
  • Redis 去重
  • 唯一索引

例如支付成功消息消费时,可用 orderId + eventType 做唯一键。


十四、如何设计分库分表

订单数据增长很快,必须考虑分库分表。

1. 为什么要分库分表

因为订单表数据量很大,单表到几千万甚至上亿后:

  • 查询性能下降
  • 索引膨胀
  • 写入压力大
  • 备份恢复困难

2. 常见分片方式

按用户 ID 分片

优点:

  • 查询用户订单很方便

缺点:

  • 平台维度统计复杂
按订单号分片

优点:

  • 分布均匀

缺点:

  • 按用户查订单要带辅助索引
按时间分表

适合归档和冷热分离。

3. 实践中常见方案

通常组合使用:

  • 库:按 user_id hash
  • 表:按月份或 order_id hash

再配合 ShardingSphere 做路由。


十五、查询性能怎么优化

订单系统读请求很多,比如:

  • 我的订单列表
  • 订单详情
  • 待付款订单
  • 售后订单
  • 商家发货列表

优化思路:

1. 索引设计

常见索引:

  • user_id + create_time
  • order_no
  • status + create_time
  • pay_status

2. 读写分离

主库写,从库读。

3. 缓存

热点订单详情可以放 Redis。

4. ES 检索

复杂条件组合查询、商家后台检索可接 Elasticsearch。

5. 冗余字段

在订单明细中冗余 sku_name、商品图片、下单价格,避免查历史订单时回源商品表。


十六、超时未支付怎么处理

这是订单系统标准能力。

典型做法:

方案一:延迟消息

订单创建后发一条延迟消息,比如 30 分钟后检查:

  • 如果未支付,关闭订单
  • 释放库存
  • 回滚优惠券

方案二:定时任务扫描

定时任务扫描超时未支付订单。

大促时通常更推荐延迟消息 + 定时补偿结合。

因为:

  • 延迟消息及时性更好
  • 定时任务可兜底

十七、高并发场景如何设计

如果是普通电商订单,核心是稳定性。
如果是大促/秒杀订单,还要额外加强:

1. 请求限流

  • 网关限流
  • 用户维度限流
  • SKU 维度限流

2. 削峰填谷

  • MQ 异步排队
  • 热点商品单独隔离

3. 热点隔离

热门商品不要跟普通商品共用完全相同的处理链路。

4. 降级

例如确认页优惠推荐、个性化标签这些非核心能力可降级,但“下单主链路”不能降。


十八、异常场景如何处理

面试里讲到这里会很加分。

1. 订单创建成功,库存锁定失败

直接回滚本地事务,返回下单失败。

2. 库存锁定成功,订单落库失败

发送补偿消息释放库存。

3. 订单已支付,但订单状态没更新

依赖支付消息重试 + 定时补偿任务。

4. 订单取消了,但库存没释放

库存释放消息重试,失败进入死信队列人工处理。

5. MQ 消息丢失

使用可靠消息机制:

  • 生产者确认
  • 消费者确认
  • 消息持久化
  • 本地消息表 / 事务消息

十九、一个推荐的面试落地回答

我会把订单系统拆成订单、库存、支付、商品、优惠券、履约等多个服务。
用户提交订单时,先做幂等校验和价格重算,再调用库存服务锁库存,成功后在订单服务本地事务里创建订单和订单明细,状态置为待支付。
下单成功后通过 MQ 异步通知支付服务生成支付单,同时发延迟消息处理超时关单。
支付成功后,支付服务发送支付成功消息,订单服务幂等消费后把订单改成已支付,再通知库存正式扣减、履约开始发货。
整个系统通过 Redis 做缓存和防重,通过 MQ 做异步解耦和最终一致性,通过分库分表支撑海量订单,通过状态机保证订单生命周期清晰可控。
一致性方面我倾向于使用本地事务 + MQ 最终一致性,而不是全链路强一致分布式事务,因为订单系统更看重吞吐、可用性和可扩展性。


二十、总结

一个好的分布式订单系统,核心不是“把订单表拆出来”这么简单,而是要解决四件事:

  • 如何在高并发下稳定地下单
  • 如何让订单、库存、支付在分布式下保持最终一致
  • 如何通过状态机把订单生命周期管清楚
  • 如何通过 MQ、缓存、分库分表让系统具备扩展性

把它理解成一句话:

订单系统是交易中台的核心编排系统,负责串联库存、支付、营销、履约等多个分布式服务,并通过状态机、幂等、消息最终一致性来保证交易链路稳定可靠。