基于RabbitMQ的企业级订单系统设计与实现文档
一、文档概述
本文档整合了多轮沟通中关于"RabbitMQ解决订单顺序错乱""库存扣减并发控制""跨服务分布式事务""企业级下单流程"的核心内容,提供从技术方案到代码落地、从流程设计到状态流转的完整实现指南,适用于Spring Cloud微服务架构下的高并发订单场景。
二、RabbitMQ解决多订单同步顺序错乱方案
2.1 问题根源与设计原则
订单顺序错乱的核心原因是"消息生产无序"或"消息消费并行",需遵循以下设计原则:
- 生产端有序:同一订单的消息(创建→支付→发货)按业务顺序同步发送,通过订单号Hash路由到固定队列;
- 消费端串行 :同一队列配置
concurrency=1(单线程消费),避免并行处理破坏顺序; - 手动ACK机制:确保消息处理完成后再确认,防止消息丢失导致顺序断层;
- 幂等性防护:基于订单号+消息序号去重,避免重试导致重复执行业务;
- 队列分区路由:高并发场景下按订单号Hash分配队列,平衡"顺序性"与"并发性能"。
2.2 技术选型
| 组件 | 版本/类型 | 作用 |
|---|---|---|
| 核心框架 | Spring Cloud 2023.0.0 | 微服务基础架构 |
| 消息中间件 | RabbitMQ 3.12+ | 消息投递与顺序控制 |
| 消息集成 | Spring Cloud Stream | 简化RabbitMQ配置与消息绑定 |
| 幂等性存储 | Redis | 订单消息去重标识存储 |
| 数据库 | MySQL | 订单与库存核心数据存储 |
2.3 核心配置与代码实现
2.3.1 生产者配置(订单服务)
yaml
spring:
application:
name: order-service
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin123
virtual-host: order-vhost
publisher-confirm-type: correlated # 消息确认机制
publisher-returns: true # 消息返回机制
cloud:
stream:
rabbit:
bindings:
order-output:
producer:
durable: true # 消息持久化
channel-transacted: true # 事务确保生产顺序
bindings:
order-output:
destination: order-exchange # 绑定交换机
producer:
exchange-type: topic # 主题交换机(支持路由)
content-type: application/json
binders:
rabbit-binder:
type: rabbit
environment:
spring: rabbitmq: {host: 127.0.0.1, port: 5672, username: admin, password: admin123, virtual-host: order-vhost}
# 队列分区配置
order:
rabbitmq:
queue-count: 3 # 3个队列实现并行
routing-key-prefix: order.route.
2.3.2 消费者配置(订单同步服务)
yaml
spring:
application:
name: order-sync-service
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin123
virtual-host: order-vhost
listener:
simple:
acknowledge-mode: manual # 手动ACK
concurrency: 1 # 单线程消费
max-concurrency: 1
prefetch: 1 # 预取1条消息,避免缓存乱序
cloud:
stream:
rabbit:
bindings:
order-input:
consumer:
durable: true
auto-bind-dlq: true # 开启死信队列(处理失败消息)
dlq-ttl: 86400000 # 死信消息过期时间(24h)
republish-to-dlq: true # 失败消息重发布到死信队列
bindings:
order-input:
destination: order-exchange
content-type: application/json
consumer:
exchange-type: topic
routing-key-pattern: order.route.* # 订阅所有订单路由键
2.3.3 核心代码(订单消息生产与消费)
(1)订单消息DTO
java
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class OrderMessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String orderNo; // 订单号(唯一标识)
private Integer orderStatus; // 订单状态(1-创建,2-支付,3-发货)
private BigDecimal amount; // 订单金额
private LocalDateTime sendTime; // 消息发送时间
private Integer msgSeq; // 消息序号(保证同订单内顺序)
}
(2)订单消息生产者(按订单号Hash路由)
typescript
import cn.hutool.core.lang.hash.MurmurHash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
@Service
public class OrderMessageProducer {
@Autowired
private OrderOutputChannel orderOutputChannel; // 自定义输出通道
@Value("${order.rabbitmq.routing-key-prefix}")
private String routingKeyPrefix;
@Value("${order.rabbitmq.queue-count}")
private Integer queueCount;
// 发送订单消息(核心:按订单号Hash路由到固定队列)
public void sendOrderMessage(OrderMessageDTO orderMessage) {
// 生成路由键:订单号Hash取模,确保同订单进同一队列
String routingKey = generateRoutingKey(orderMessage.getOrderNo());
// 构建并发送消息
org.springframework.messaging.Message<OrderMessageDTO> message = MessageBuilder
.withPayload(orderMessage)
.setHeader("spring.cloud.stream.sendto.destination", routingKey)
.build();
orderOutputChannel.output().send(message);
}
// 生成路由键(MurmurHash确保分布均匀)
private String generateRoutingKey(String orderNo) {
int hash = MurmurHash.hash32(orderNo);
int queueIndex = Math.abs(hash) % queueCount;
return routingKeyPrefix + queueIndex;
}
}
(3)订单消息消费者(单线程+手动ACK)
java
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Service;
@Service
public class OrderMessageConsumer {
@Autowired
private IdempotentHandler idempotentHandler; // 幂等性工具类
@StreamListener(OrderInputChannel.ORDER_INPUT)
public void consumeOrderMessage(OrderMessageDTO message, Channel channel, Message amqpMessage) throws Exception {
long deliveryTag = amqpMessage.getMessageProperties().getDeliveryTag();
try {
// 1. 幂等性检查:避免重复消费
boolean isFirstProcess = idempotentHandler.checkAndSet(message.getOrderNo(), message.getMsgSeq());
if (!isFirstProcess) {
channel.basicAck(deliveryTag, false);
return;
}
// 2. 业务处理(按订单状态执行同步逻辑)
processOrderBusiness(message);
// 3. 手动ACK:处理成功确认消息
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 4. 手动NACK:处理失败,消息进入死信队列
channel.basicNack(deliveryTag, false, false);
}
}
// 订单业务处理(创建/支付/发货逻辑)
private void processOrderBusiness(OrderMessageDTO message) {
switch (message.getOrderStatus()) {
case 1:
System.out.printf("同步订单创建:%s%n", message.getOrderNo());
break;
case 2:
System.out.printf("同步订单支付:%s%n", message.getOrderNo());
break;
case 3:
System.out.printf("同步订单发货:%s%n", message.getOrderNo());
break;
}
}
}
三、多订单库存扣减并发控制
3.1 库存扣减核心风险
- 库存超卖:多订单同时读取库存并扣减,导致库存为负;
- 数据不一致:库存扣减成功但订单处理失败,或反之;
- 跨服务耦合:库存作为独立服务时,同步调用易导致超时或服务依赖故障。
3.2 解决方案:Redis预扣减+分布式锁+乐观锁
3.2.1 技术原理
- Redis预扣减:缓存商品库存,高并发下快速判断库存是否充足,减少数据库访问;
- 分布式锁(Redisson) :锁定商品维度,防止多订单同时扣减同一商品库存;
- 数据库乐观锁 :通过
version字段确保库存扣减的原子性,避免并发写冲突。
3.2.2 核心代码实现
(1)库存实体与数据库设计
sql
-- 商品库存表(含乐观锁)
CREATE TABLE product_stock (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id VARCHAR(32) NOT NULL COMMENT '商品ID',
stock_num INT NOT NULL COMMENT '当前库存',
locked_num INT NOT NULL DEFAULT 0 COMMENT '已锁定库存(未支付订单占用)',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
UNIQUE KEY uk_product_id (product_id)
);
(2)库存扣减服务
typescript
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class StockDeductService {
@Autowired
private ProductStockRepository stockRepository;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private StockLockUtil stockLockUtil; // 分布式锁工具类
private static final String REDIS_STOCK_KEY = "product:stock:";
// 库存扣减核心方法
public boolean deductStock(String productId, Integer buyNum, String orderNo) {
// 1. Redis预判断库存
String redisKey = REDIS_STOCK_KEY + productId;
String stockStr = redisTemplate.opsForValue().get(redisKey);
if (stockStr == null) loadStockToRedis(productId); // 缓存未命中,从DB加载
if (Integer.parseInt(stockStr) < buyNum) return false;
// 2. 获取分布式锁(锁定商品)
RLock lock = stockLockUtil.lock(productId, 3, 10); // 等待3s,持有10s
if (lock == null) return false;
try {
// 3. 数据库双重校验+乐观锁扣减
ProductStock stock = stockRepository.findByProductId(productId)
.orElseThrow(() -> new RuntimeException("商品不存在:" + productId));
if (stock.getStockNum() < buyNum) return false;
// 乐观锁扣减:version不匹配则更新失败(返回0)
int affectedRows = stockRepository.deductStock(stock.getId(), buyNum, stock.getVersion());
if (affectedRows != 1) return false;
// 4. 更新Redis缓存
redisTemplate.opsForValue().decrement(redisKey, buyNum);
return true;
} finally {
stockLockUtil.unlock(lock); // 释放锁
}
}
// 从DB加载库存到Redis
private void loadStockToRedis(String productId) {
ProductStock stock = stockRepository.findByProductId(productId)
.orElseThrow(() -> new RuntimeException("商品不存在:" + productId));
redisTemplate.opsForValue().set(REDIS_STOCK_KEY + productId, stock.getStockNum().toString());
}
// 库存回滚(订单取消时调用)
@Transactional(rollbackFor = Exception.class)
public void rollbackStock(String productId, Integer buyNum) {
ProductStock stock = stockRepository.findByProductId(productId)
.orElseThrow(() -> new RuntimeException("商品不存在:" + productId));
stock.setStockNum(stock.getStockNum() + buyNum);
stockRepository.save(stock);
redisTemplate.opsForValue().increment(REDIS_STOCK_KEY + productId, buyNum);
}
}
(3)分布式锁工具类
java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class StockLockUtil {
@Autowired
private RedissonClient redissonClient;
// 获取商品库存锁
public RLock lock(String productId, long waitTime, long leaseTime) {
String lockKey = "stock:lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS) ? lock : null;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
// 释放锁
public void unlock(RLock lock) {
if (lock != null && lock.isHeldByCurrentThread()) lock.unlock();
}
}
四、跨服务分布式事务与一致性保障
4.1 核心问题:跨服务原子性
订单服务(A)与库存服务(B)独立部署,需确保"订单预创建+库存扣减"要么同时成功,要么同时失败,需通过分布式事务解决。
4.2 最优方案:可靠消息+最终一致性
4.2.1 方案原理
基于RabbitMQ实现"可靠消息投递+消费确认+定时补偿",确保跨服务数据最终一致:
- 可靠消息投递:订单服务预创建订单后,通过事务确保消息必入队;
- 可靠消息消费:库存服务消费消息并执行扣减,处理完成后发送结果消息;
- 结果处理:订单服务消费结果消息,更新订单状态(成功确认/失败回滚);
- 定时补偿:处理消息丢失或延迟导致的不一致。
4.2.2 跨服务流程设计
css
graph TD
A[用户下单] --> B[订单服务:预创建订单(状态0:待确认)]
B --> C[订单服务:发送库存扣减消息(事务确保投递)]
C --> D[库存服务:消费消息,执行扣减]
D --> E{扣减成功?}
E -->|是| F[库存服务:发送"扣减成功"消息]
E -->|否| G[库存服务:发送"扣减失败"消息]
F --> H[订单服务:更新订单状态为1(待支付)]
G --> I[订单服务:删除预创建订单(回滚)]
J[定时补偿任务] --> K[查询超时"待确认"订单]
K --> L[调用库存服务查询扣减状态]
L --> M{已扣减?}
M -->|是| H
M -->|否| I
4.2.3 核心代码实现
(1)订单服务:预创建订单与消息发送
scss
@Service
public class OrderService {
@Autowired
private OrderMainRepository orderRepository;
@Autowired
private StockMessageChannel stockMessageChannel; // 库存消息通道
// 核心下单流程:预创建订单→发送库存扣减消息
@Transactional(rollbackFor = Exception.class)
public boolean createOrder(String orderNo, String productId, Integer buyNum) {
try {
// 1. 预创建订单(状态0:待确认,未占用库存)
OrderMain preOrder = new OrderMain();
preOrder.setOrderNo(orderNo);
preOrder.setProductId(productId);
preOrder.setBuyNum(buyNum);
preOrder.setOrderStatus(0); // 0-待确认
orderRepository.save(preOrder);
// 2. 发送库存扣减消息(事务确保:订单预创建成功则消息必发)
StockDeductRequestDTO request = new StockDeductRequestDTO();
request.setOrderNo(orderNo);
request.setProductId(productId);
request.setBuyNum(buyNum);
request.setMessageId(IdUtil.fastSimpleUUID()); // 消息唯一ID(幂等)
stockMessageChannel.stockDeductOutput().send(
MessageBuilder.withPayload(request).build()
);
return true;
} catch (Exception e) {
return false; // 异常时本地事务回滚,预创建订单删除
}
}
}
(2)库存服务:消费扣减消息并返回结果
scss
@Service
public class StockDeductHandler {
@Autowired
private StockDeductService stockDeductService;
@Autowired
private StockMessageChannel stockMessageChannel;
@Autowired
private IdempotentHandler idempotentHandler;
@StreamListener(StockMessageChannel.STOCK_DEDUCT_INPUT)
public void handleStockDeduct(StockDeductRequestDTO request, Channel channel, Message amqpMessage) throws Exception {
long deliveryTag = amqpMessage.getMessageProperties().getDeliveryTag();
String messageId = request.getMessageId();
try {
// 1. 幂等性检查:避免重复扣减
if (!idempotentHandler.checkAndSet(messageId, 1)) {
channel.basicAck(deliveryTag, false);
return;
}
// 2. 执行库存扣减
boolean success = stockDeductService.deductStock(
request.getProductId(), request.getBuyNum(), request.getOrderNo()
);
// 3. 发送扣减结果
StockDeductResultDTO result = new StockDeductResultDTO();
result.setOrderNo(request.getOrderNo());
result.setMessageId(messageId);
result.setSuccess(success);
stockMessageChannel.stockResultOutput().send(
MessageBuilder.withPayload(result).build()
);
// 4. 确认消息
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 5. 失败处理:发送失败结果+拒绝消息
StockDeductResultDTO failResult = new StockDeductResultDTO();
failResult.setOrderNo(request.getOrderNo());
failResult.setMessageId(messageId);
failResult.setSuccess(false);
stockMessageChannel.stockResultOutput().send(MessageBuilder.withPayload(failResult).build());
channel.basicNack(deliveryTag, false, false);
}
}
}
(3)订单服务:消费扣减结果并更新状态
scss
@Service
public class StockResultHandler {
@Autowired
private OrderMainRepository orderRepository;
@StreamListener(StockMessageChannel.STOCK_RESULT_INPUT)
@Transactional(rollbackFor = Exception.class)
public void handleStockResult(StockDeductResultDTO result) {
String orderNo = result.getOrderNo();
OrderMain order = orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new RuntimeException("订单不存在:" + orderNo));
if (result.isSuccess()) {
// 扣减成功:更新订单状态为1(待支付)
order.setOrderStatus(1);
order.setStockDeductStatus(1);
} else {
// 扣减失败:删除预创建订单(回滚)
orderRepository.delete(order);
}
orderRepository.save(order);
}
}
(4)定时补偿任务(确保最终一致性)
scss
@Component
public class OrderCompensateTask {
@Autowired
private OrderMainRepository orderRepository;
@Autowired
private StockFeignClient stockFeignClient; // 调用库存服务的Feign接口
// 每5分钟执行:处理超时"待确认"订单
@Scheduled(cron = "0 0/5 * * * ?")
@Transactional(rollbackFor = Exception.class)
public void compensatePendingOrder() {
LocalDateTime tenMinutesAgo = LocalDateTime.now().minus(10, ChronoUnit.MINUTES);
// 查询10分钟前仍为"待确认"的订单
List<OrderMain> pendingOrders = orderRepository.findByOrderStatusAndCreateTimeBefore(0, tenMinutesAgo);
for (OrderMain order : pendingOrders) {
// 调用库存服务查询扣减状态
boolean hasDeducted = stockFeignClient.checkStockDeducted(order.getOrderNo());
if (hasDeducted) {
// 库存已扣减:更新订单状态
order.setOrderStatus(1);
order.setStockDeductStatus(1);
orderRepository.save(order);
} else {
// 库存未扣减:删除订单
orderRepository.delete(order);
}
}
}
}
五、企业级下单全流程与订单状态流转
5.1 预创建订单与普通创建的区别
| 对比维度 | 预创建订单(临时订单) | 普通创建订单(最终订单) |
|---|---|---|
| 核心目的 | 占订单号、为跨服务回滚留出口 | 确认交易生效,支持后续履约(支付/发货) |
| 状态属性 | 临时状态(可删除/回滚) | 正式状态(不可随意修改) |
| 资源占用 | 不占用库存、优惠券等核心资源 | 占用库存、锁定优惠券 |
| 回滚成本 | 极低(直接删除临时记录) | 极高(需回滚库存、解锁资源) |
| 业务意义 | 告知用户"订单处理中" | 告知用户"订单创建成功" |
5.2 订单核心状态定义
| 状态值 | 状态名称 | 触发条件 | 业务含义 |
|---|---|---|---|
| 0 | 待确认(预创建) | 用户提交订单,订单服务预创建记录 | 订单临时占位,等待库存扣减等前置条件 |
| 1 | 待支付 | 库存扣减成功,订单服务更新状态 | 订单正式创建,等待用户支付 |
| 2 | 已支付/待发货 | 用户完成支付,支付服务通知订单服务 | 支付完成,等待商家发货 |
| 3 | 已发货 | 商家操作发货,订单服务同步物流信息 | 商品已发出,等待用户收货 |
| 4 | 已完成 | 用户确认收货或系统超时自动确认 | 订单闭环,交易完成 |
| 5 | 已取消 | 用户取消未支付订单,或退款完成 | 订单终止,资源(库存/优惠券)回滚 |
| 6 | 创建失败 | 库存扣减失败、参数校验失败等 | 订单未创建成功,无资源占用 |
5.3 完整状态流转链路
5.3.1 正常下单链路
用户提交订单 → 预创建订单(0:待确认) → 库存扣减成功 → 订单状态1(待支付) → 用户支付成功 → 订单状态2(已支付/待发货) → 商家发货 → 订单状态3(已发货) → 用户确认收货 → 订单状态4(已完成)。
5.3.2 异常场景链路
- 库存不足:用户提交订单 → 预创建订单(0) → 库存扣减失败 → 删除订单 → 状态6(创建失败);
- 支付超时:订单状态1(待支付) → 超时未支付 → 订单状态5(已取消) → 库存回滚;
- 支付后退款:订单状态2(已支付) → 用户申请退款 → 商家同意 → 支付服务退款 → 订单状态5(已取消) → 库存回滚。
六、总结
- 订单顺序控制:基于RabbitMQ队列分区+单线程消费,确保同订单串行、多订单并行,解决顺序错乱问题;
- 库存并发控制:通过Redis预扣减+Redisson分布式锁+数据库乐观锁,防止超卖并保证数据一致性;
- 跨服务事务:采用"可靠消息+最终一致性"方案,结合预创建订单和定时补偿,解决跨服务原子性问题;
- 企业级流程:以"预创建→前置校验→确认订单→履约"为核心,通过状态流转实现订单全生命周期管理,兼顾高并发与数据一致性。
本方案可直接落地到Spring Cloud微服务架构的订单系统,支持高并发场景下的稳定运行,同时具备良好的扩展性(如新增优惠券、物流等模块可复用消息机制与事务方案)。