rabbitmq延时队列自动解锁库存

一、库存服务自动解锁库存

使用了最终一致性来解决分布式事务

当order服务出现异常回滚,此时ware服务无法回滚,怎么办?

使用seata全局事务虽然能在order服务出现异常导致回滚 时使其他服务的也能同时回滚,但在流量大的情况下是使用加锁的方式,效率

低不适合并发量大的情况,也可以使用定时任务轮询去查看订单的状态,但是轮询的方式比较占资源和内存,所以选用最终一致性的方案,使用mq延时队列死信路由,然后做出补救方案,只要订单服务出现故障就通过mq定时去判断,只要能保证库存最终能解锁即可

延时队列自动解锁库存业务逻辑

ware服务在完成锁库存时就给mq发消息,把消息存到死信队列中,这个消息记录了那些商品锁定多少库存,当queue到达存活时间就会把消息交给死信路由交换机,死信路由交换机会把消息发到最终的队列,如果订单支付时间为30分钟,我们就把存活时间设置为40分钟,这样就能保证我们监听的消息一定是超过了支付的时间的,然后让ware库存服务去订阅监听最终的队列即可,只要有消息我们就去检验order订单服务,只要证明订单服务出现异常回滚或者订单超过支付时间未支付的订单我们就去做一个解锁还原库存的操作

1.库存锁定成功,给mq发消息

(1)保存 工作单(订单号)、工作单详情(商品锁了多少库存)

(2)把上面的数据给mq发一份

java 复制代码
@Transactional
    @Override
    public boolean orderStock(OrderStockRequest orderStockRequest) {
        //保存工作单
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderSn(orderStockRequest.getOrderSn());
        wareOrderTaskService.save(wareOrderTaskEntity);

        List<OrderItemVo> itemVos = orderStockRequest.getItemVos();
        List<SkuStockfromWare> collect = itemVos.stream().map(item -> {
            SkuStockfromWare skuStockfromWare = new SkuStockfromWare();
            skuStockfromWare.setSkuId(item.getSkuId());
            skuStockfromWare.setNum(item.getCount());
            //查询该商品在那些仓库有库存
            List<Long> wareId = wareSkuDao.skuStockfromWare(item.getSkuId());
            skuStockfromWare.setWareId(wareId);
            return skuStockfromWare;
        }).collect(Collectors.toList());

        //根据skuId遍历
        for (SkuStockfromWare skuStockfromWare : collect) {
            //判断是否锁定成功
            boolean flag = false;

            //判断该商品是否有仓库存在库存
            List<Long> wareIdList = skuStockfromWare.getWareId();
            if (wareIdList.size() < 0 || wareIdList == null){
                throw new NoWareStockException(skuStockfromWare.getSkuId());
            }
            for (Long wareId : wareIdList) {
                Long count = wareSkuDao.LockedStockFromWare(skuStockfromWare.getSkuId(),wareId,skuStockfromWare.getNum());
                if (count.equals(1L)){
                    //锁定成功
                    flag = true;

                    //保存工作单详情
                    WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
                    wareOrderTaskDetailEntity.setSkuId(skuStockfromWare.getSkuId());
                    wareOrderTaskDetailEntity.setSkuNum(skuStockfromWare.getNum());
                    wareOrderTaskDetailEntity.setTaskId(wareOrderTaskEntity.getId());
                    wareOrderTaskDetailEntity.setWareId(wareId);
                    wareOrderTaskDetailEntity.setLockStatus(1);
                    wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);
                    //TODO 库存锁定成功->发消息给交换机
                    StockLocked stockLocked = new StockLocked();
                    stockLocked.setTaskId(wareOrderTaskEntity.getId());
                    WareOrderTaskDetailTo wareOrderTaskDetailTo = new WareOrderTaskDetailTo();
                    BeanUtils.copyProperties(wareOrderTaskDetailEntity,wareOrderTaskDetailTo);
                    stockLocked.setDetailTo(wareOrderTaskDetailTo);
                    //convertAndSend(String exchange, String routingKey, Object object)
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",);

                    //该商品锁定库存成功就执行下一个商品
                    break;
                }

            }

            //如果没有一个仓库扣成功,代表此skuId的库存不足
            if (!flag){
                throw new SkuNoStockException(skuStockfromWare.getSkuId());
            }

        }
        return true;
    }

2.监听队列,解锁库存

(1)判断工作单是否存在

不存在代表锁库存操作已回滚,不做处理

(2)查询订单是否存在

如果订单不存在,表示下订单操作已回滚,执行解锁库存操作

如果存在,查询订单状态是否为 4-已关闭,如果是 4-已关闭,执行解锁库存操作,订单其他状态不做处理

(3)解锁前判断工作单的状态是否为 1-已锁定,证明只做了锁定库存操作

(4)解锁库存,修改工作单详情状态为 已解锁

java 复制代码
/**
 * 解锁库存
 */
@RabbitListener(queues = {"stock.release.stock.queue"})
@Service
public class UnLockStockListener {

    @Autowired
    WareSkuService wareSkuService;

    @RabbitHandler
    public void UnLockStock(StockLockedTo lockedTo, Channel channel, Message message) throws IOException {
        try {
            wareSkuService.unlockStock(lockedTo);
            //签收
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        } catch (Exception e) {
            //拒签,让消息重新归队,等待服务器重启进行下一次解锁
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }

    }

}

解锁操作

java 复制代码
    /**
     * 解锁库存
     *
     * (1).判断工作单是否存在
     *      不存在代表已回滚,不做处理
     *      (2).查询订单是否存在
     *          如果订单不存在,表示已回滚
     *             (3).执行解锁库存操作
     *          如果存在,查询订单状态是否为 4-已关闭
     *                如果是 4-已关闭,执行解锁库存操作,订单其他状态不做处理
     */
    @Override
    public void unlockStock(StockLockedTo lockedTo) {

        WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(lockedTo.getTaskId());
        //已回滚不做处理
        if (taskEntity != null){
            //查询订单是否存在
            R<OrderVo> r = orderFeignService.orderStatus(taskEntity.getOrderSn());
            if (r.getCode() == 0){
                OrderVo orderVo = r.getData(new TypeReference<OrderVo>() {
                });
                if (orderVo == null || orderVo.getStatus() == 4){
                    WareOrderTaskDetailTo detailTo = lockedTo.getDetailTo();
                    //判断工作单的状态是否为 1-已锁定,证明只做了锁定库存操作
                    if (detailTo.getLockStatus() == 1){
                        //恢复库存
                        unlock(detailTo.getId(),detailTo.getSkuNum(),detailTo.getSkuId(),detailTo.getWareId());
                    }
                }
            }else {
                throw new OrderFeignException();
            }

        }
    }

    /**
     * 解锁库存
     * UPDATE `wms_ware_sku` SET stock_locked = stock_locked - ?
     * WHERE sku_id = ? AND ware_id = ?
     */
    private void unlock(Long id,Integer skuNum, Long skuId, Long wareId) {
        wareSkuDao.unlock(skuNum,skuId,wareId);
        //修改状态为 已解锁
        WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
        wareOrderTaskDetailEntity.setId(id);
        wareOrderTaskDetailEntity.setLockStatus(2);
    }

二、订单服务关闭订单同时也执行解锁库存操作

相关推荐
回家路上绕了弯13 小时前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户83071968408216 小时前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840823 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者4 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者6 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧7 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖7 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农7 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者7 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀7 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式