电商秒杀之交易性能优化技术——事务型消息

上一部分使用异步消息的方式将Redis库存和对应的数据库内的库存达到了最终的一致性

落单减库存对应的decreaseStock操作做了改造,改造结果为从Redis中扣,扣完之后发送异步消息给库存的一个异步回调,然后在异步回调内扣减对应的数据库。若异步消息发生任何异常,把对应的Redis库存加了回来,或者Redis库存更新失败,也将对应的Redis库存加了回来

问题是:decreaseStock依赖于外部的一个事务,如果说外部已经有了事务,@Transactional标签就会沿用外部事务,也就是和外部事务同时成功同时失败。但是因为整个下单操作是属于同一个 transaction事务的,如果用户下单成功,但是之后订单入库或返回前端的过程中发生错误了,事务回滚,会导致少卖的现象,有可能造成库存堆积。

少卖也就是事务 (@Transactional) 回滚只针对mysql数据库,而对redis不起作用,就会出现redis减库存了,而mysql数据库事务回滚后,库存并没有减少。

解决方法就是异步消息的发送要在整个事务完全成功后再Redis扣减,然后发送。

一.事务型消息

1.在ItemService.java中添加方法

    //库存回滚
    boolean increaseStock(Integer itemId, Integer amount) throws BusinessException;

    //异步更新库存
    boolean asyncDecreaseStock(Integer itemId, Integer amount);

2.ItemServiceImpl.java

 @Override
    @Transactional
    public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
        //将stock库存表中对应的商品id的对应库存数量减去购买数量
        //increment(-3)等于减去3,返回的是计算后的值
        long result = redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue() * -1);
        if (result >= 0) {
            //更新库存成功,发送消息,减数据库的库存
           //更新库存成功直接true
            return true;
        } else {
            // 更新库存失败,需要回滚redis库存,将库存重新补回去
            increaseStock(itemId, amount);
            return false;
        }
    }
    
    // redis库存回补
    @Override
    public boolean increaseStock(Integer itemId, Integer amount) throws BusinessException {
        redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());
        return true;
    }

    // 异步更新库存,使用rocketmq实现
    @Override
    public boolean asyncDecreaseStock(Integer itemId, Integer amount) {
        //更新库存成功,发送消息,减数据库的库存
        boolean mqResult = mqProducer.asyncReduceStock(itemId, amount);
        return mqResult;
    }

3.OrderServiceImpl.java


           //加上商品销量
        itemService.increaseSales(itemId,amount);

        //异步更新库存
         boolean mqResult =itemService.asyncDecreaseStock(itemId,amount);
         if(!mqResult){
             itemService.increaseStock(itemId,amount);
             throw new BusinessException(EmBusinessError.MQ_SEND_FAIL);
         }

        //4.返回前端
        return orderModel;

4.OrderController.java

 @Autowired
    private MqProducer mqProducer;
     if (userModel == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登录,不能下单");
        }

        //   OrderModel orderModel = orderService.createOrder(userModel.getId(),itemId, promoId, amount);
        // 创建订单,开启异步发送事务型消息的操作
        if (!mqProducer.transactionAsyncReduceStock(userModel.getId(), itemId, promoId, amount)) {
            throw new BusinessException(EmBusinessError.UNKNOWN_ERROR, "下单失败");
        }


        return CommonReturnType.create(null);
    }

5.MqProducer.java

    private OrderService orderService;
  @PostConstruct
    public void init() throws MQClientException {
        // 做mq producer的初始化
        producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr(nameAddr);
        producer.start();

        transactionMQProducer = new TransactionMQProducer("transaction_producer_group");
        transactionMQProducer.setNamesrvAddr(nameAddr);
        transactionMQProducer.start();
        transactionMQProducer.setTransactionListener(new TransactionListener() {

            // 发送事务型消息,消息的类型是prepare,不会被consumer立即执行
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object arg) {
                // 真正要做的事,创建订单
                Integer userId = (Integer) ((Map) arg).get("userId");
                Integer itemId = (Integer) ((Map) arg).get("itemId");
                Integer promoId = (Integer) ((Map) arg).get("promoId");
                Integer amount = (Integer) ((Map) arg).get("amount");
                try {
                    // 这里进行订单的创建
                    orderService.createOrder(userId, itemId, promoId, amount);
                } catch (BusinessException e) {
                    e.printStackTrace();
                    // 如果订单创建失败则事务回滚
                    // ROLLBACK_MESSAGE表示将之前的prepare消息撤回,相当于没发送消息
                    return LocalTransactio
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值