一、由于下单涉及到库存服务和订单服务,为了综合分布式事务控制和并发问题,选用柔性事务-可靠消息-最终一致性(异步确保型)控制下订单的分布式事务。具体采用 RabbitMQ延迟队列(消息队列TTL+死信队列)实现。

分布式事务控制:
当提交订单时,当库存已经锁定的后续业务执行异常,本地事务控制只能回滚对订单服务连接的数据库相应的操作,而库存服务无法感知到异常,那么锁定的库存就永远无法释放。
而选取RabbitMQ对事物控制的逻辑如下:当库存服务成功锁定库存时,会给延迟队列stock.delay.queue发送消息,这个队列会设置一个TTL(消息在队列中的存活时间),这个队列不会被别人监听,当TTL结束时,消息中的队队列不会乱丢,他会将过期的消息发送到死信队列 stock.release.stock.queue中,用户只需要监听这个队列,消费队列中的消息,消费逻辑如下:
// 解锁 // 查询数据库 关于这个订单的锁库存信息 // 有:证明库存锁定成功 // 解锁:订单情况 // 没有订单:说明订单逻辑异常回滚了,那么库存也应该回滚 // 有订单:说明订单逻辑也成功了 // 订单状态: // 已取消:解锁库存 // 没取消:不能解锁 // 没有:库存锁定失败 库存事务回滚,无需解锁
public void unLockStock(StockLockedTo to) {
System.out.println("收到解锁库存的消息....");
StockDetailTo detailTo = to.getDetailTo();
// 只要解锁库存的消息失败 一定要告诉服务器解锁失败
WareOrderTaskDetailEntity detailEntity = wareOrderTaskDetailService.getById(detailTo.getId()); // 查询数据库 关于这个订单的锁库存信息
if(detailEntity!=null){
// 远程查询订单情况
WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(to.getId());
R r = orderFeignService.getOrderByOrderSn(taskEntity.getOrderSn()); // 订单信息
if(r.getCode() == 0) { // 成功调用
OrderVo data = r.getData(new TypeReference<OrderVo>() {
});
if (data == null || data.getStatus() == 4) { //订单被取消
// 解锁库存
if(detailEntity.getLockStatus() == 1){ // 工作单状态是 已锁定 才会解锁
unLockStock(detailEntity.getSkuId(), detailEntity.getWareId(), detailEntity.getSkuNum(), detailEntity.getId());
}
}
}else{
throw new RuntimeException("远程服务失败");
}
}
}
由此来控制下单逻辑中库存服务和订单服务的事务。同时也能对已经取消的订单进行库存解锁。
二、考虑一种业务逻辑,用户下单后,如果一直不去支付,那么系统就应该在指定的时间内取消订单,因此再次使用RabbitMQ,下单成功后,会自动给队列order-delay-queue发送下单成功消息,该队列同样不被任何用户监听,当这个延迟队列的消息TTL结束后,会将过期的消息保存到死信队列order.release.order.queue中。用户只需要监听这个队列,消息处理逻辑如下。
public void closeOrder(OrderEntity orderEntity) {
// 查询订单最新状态
OrderEntity order = this.getById(orderEntity.getId());
if(order.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){ // 待付款状态才关单
OrderEntity updateEntity = new OrderEntity();
updateEntity.setId(orderEntity.getId());
updateEntity.setStatus(OrderStatusEnum.CANCLED.getCode()); //设置状态为取消状态
this.updateById(updateEntity);
// MQ发送一个订单释放的消息
OrderTo orderTo = new OrderTo();
BeanUtils.copyProperties(order,orderTo);
rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);
}
}
为什么上面修改了订单状态后还要个MQ发送一个订单释放的消息呢?
我们之前成功创建订单时肯定给队列stock.delay.queue发送过消息了,只需要将stock.delay.queue这个队列的TTL时间比order.release.order.queue时间长就可以了,order.release.order.queue的消息应该先被消费了,订单状态被修改为已取消状态了,此后消费stock.delay.queue消息时,会发现订但状态时取消状态,他就会去解锁库存。
但是考虑一种问题:订单创建成功给MQ发送消息时由于网络原因,很久都没有发送过去,而库存锁定成功的消息却已经发送过去,这就可能导致stock.delay.queue里面的消息会先一步被消费,此时查询订单转状态为新建状态,他就不会去释放内存,而后面order.release.order.queue里面的消息在被消费时,订单状态虽然变成已取消,但是库存也就永远无法释放了。
因此我们在订单超时消息处理结束后给MQ的stock.release.stock.queue队列在发送一个消息,表示某个订单已经释放,监听stock.release.stock.queue这个队里的客户端再去消费这个消息,主动去解锁库存。后面即使stock.delay.queue的TTl到期,他也会发现锁库存工作单的状态是已解锁了,不会再去解锁。逻辑如下:
public void unLockStock(OrderTo to) {
String orderSn = to.getOrderSn();
// 解放库存
// 查询最新的库存解锁状态 防止重复解锁库存
WareOrderTaskEntity taskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
// 按照工作单 找到所有没有解锁的库存
List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list(
new QueryWrapper<WareOrderTaskDetailEntity>()
.eq("task_id", taskEntity.getId())
.eq("lock_status", 1));
for (WareOrderTaskDetailEntity orderTaskDetailEntity : list) {
unLockStock(orderTaskDetailEntity.getSkuId(),orderTaskDetailEntity.getWareId(),orderTaskDetailEntity.getSkuNum(),orderTaskDetailEntity.getTaskId());
}
}
@Transactional
public void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId){
this.baseMapper.unLockStock(skuId,wareId,num);
// 更新 库存工作单状态 为已解锁
WareOrderTaskDetailEntity orderTaskDetailEntity = new WareOrderTaskDetailEntity();
orderTaskDetailEntity.setId(taskDetailId);
orderTaskDetailEntity.setLockStatus(2);
wareOrderTaskDetailService.updateById(orderTaskDetailEntity);
}
本文介绍了如何使用RabbitMQ的延迟队列和死信队列实现分布式事务控制,确保在下单过程中库存服务和订单服务的最终一致性。通过TTL机制和解锁订单逻辑,解决了并发问题和异常处理中的库存释放问题。

1519

被折叠的 条评论
为什么被折叠?



