# 《Seckill秒杀系统》第58章:异步化扣减商品库存编码实现

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

  • 本章难度:★★★☆☆
  • 本章重点:重点理解并掌握异步化扣减商品库存的编码实现,掌握订单微服务和商品微服务之间数据的交互实现,并能够灵活运用到自身实际项目中。

大家好,我是冰河~~

在秒杀系统中,异步化扣减商品库存时,需要经过精心的设计,采用异步方式扣减商品库存,稍有不慎,就可能会出现商品超卖或者少卖的问题。一旦出现商品超卖或者少卖,那后果就比较严重了。

# 一、前言

在秒杀系统中,各个交易链路上,我们都要考虑数据的一致性问题和性能问题。用户从进入秒杀活动专场选择商品,到商品详情,再到抢购下单,整个流程都要考虑系统的并发和性能,对于核心交易链路,比如抢购下单,扣减库存,更是要在考虑性能的同时,也要考虑数据的一致性问题。对于下单扣减库存来说,采用异步化设计能够有效提升整体核心交易链路的性能。

# 二、本章诉求

重新梳理秒杀系统中抢购下单并扣减商品库存的核心交易链路的实现,重点掌握下单扣减库存的异步化设计与实现,同时,重新梳理并实现基于可靠消息最终一致性方案解决下单扣减库存出现的分布式事务问题,并能将本章中问题的解决方案灵活运用到自身实际项目中。

# 三、异步扣减商品库存编码实现

本节,我们还是以Lua脚本防止库存超卖为例实现下单扣减商品库存的异步化,从本质上来说,基于可靠消息最终一致性方案解决的下单扣减商品库存产生的分布式事务问题,就是一种异步化的设计。

# 3.1 正向流程编码实现

秒杀系统下单扣减库存的业务,整合可靠消息最终一致性分布式事务解决方案的正向流程可以分成订单微服务整合分布式事务和商品微服务整合分布式事务两个方面。

1.订单微服务整合分布式事务

订单微服务整合可靠消息最终一致性分布式事务正向流程的步骤如下所示。

(1)修改SeckillPlaceOrderService接口

主要是在SeckillPlaceOrderService接口中新增了订单微服务执行本地事务的saveOrderInTransaction()方法和构建订单的buildSeckillOrder()方法。

SeckillPlaceOrderService接口的源码详见:seckill-order-application工程下的io.binghe.seckill.order.application.place.SeckillPlaceOrderService。

void saveOrderInTransaction(TxMessage txMessage);

default SeckillOrder buildSeckillOrder(Long userId, SeckillOrderCommand seckillOrderCommand, SeckillGoodsDTO seckillGoods){
    SeckillOrder seckillOrder = new SeckillOrder();
    BeanUtil.copyProperties(seckillOrderCommand, seckillOrder);
    seckillOrder.setId(SnowFlakeFactory.getSnowFlakeFromCache().nextId());
    seckillOrder.setGoodsName(seckillGoods.getGoodsName());
    seckillOrder.setUserId(userId);
    seckillOrder.setActivityPrice(seckillGoods.getActivityPrice());
    BigDecimal orderPrice = seckillGoods.getActivityPrice().multiply(BigDecimal.valueOf(seckillOrder.getQuantity()));
    seckillOrder.setOrderPrice(orderPrice);
    seckillOrder.setStatus(SeckillOrderStatus.CREATED.getCode());
    seckillOrder.setCreateTime(new Date());
    return seckillOrder;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

(2)修改SeckillPlaceOrderLuaService类

整合RocketMQ事务消息时,会先将Half消息发送到RocketMQ,发送成功后会回调执行本地事务,所以,在SeckillPlaceOrderLuaService中实现SeckillPlaceOrderService接口中新增的saveOrderInTransaction()方法。

SeckillPlaceOrderLuaService类的源码详见:seckill-order-application工程下的io.binghe.seckill.order.application.place.impl.SeckillPlaceOrderLuaService。

@Override
@Transactional(rollbackFor = Exception.class)
public void saveOrderInTransaction(TxMessage txMessage) {
    try{
        Boolean submitTransaction = distributedCacheService.hasKey(SeckillConstants.getKey(SeckillConstants.ORDER_TX_KEY, String.valueOf(txMessage.getTxNo())));
        if (BooleanUtil.isTrue(submitTransaction)){
            logger.info("saveOrderInTransaction|已经执行过本地事务|{}", txMessage.getTxNo());
            return;
        }
        //构建订单
        SeckillOrder seckillOrder = this.buildSeckillOrder(txMessage);
        //保存订单
        seckillOrderDomainService.saveSeckillOrder(seckillOrder);
        //保存事务日志
        distributedCacheService.put(SeckillConstants.getKey(SeckillConstants.ORDER_TX_KEY, String.valueOf(txMessage.getTxNo())), txMessage.getTxNo(), SeckillConstants.TX_LOG_EXPIRE_DAY, TimeUnit.DAYS);
    }catch (Exception e){
        logger.error("saveOrderInTransaction|异常|{}", e.getMessage());
        distributedCacheService.delete(SeckillConstants.getKey(SeckillConstants.ORDER_TX_KEY, String.valueOf(txMessage.getTxNo())));
        this.rollbackCacheStack(txMessage);
        throw e;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 查看完整文章

加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码