工作中redis锁的问题记录

受到公司刚大佬的影响,说了很多关于写总结的好处,于是我就试着写一写微笑



上次上线的新车2,版本,有这么一个问题,我们服务端给app提供了一个上传图片的接口,图片是接受参数是一个数组,意思是等图片上传完之后,统一将图片id给我们就好了,但是因为上传接货单的上限是50张图片,所有如果一次就将图片全部上传并调用我们接口的话,app那边反应将会很慢,所以最后定的方案就是上传一张接货单就调用我们一次接口。

我们服务这边,是第一次上传借货单的时候就去创建一张接货单审核单,一个物流单对应一个物流单审核单,意味着一次上传的图片都挂在接货单审核单下面,因为app那边是上传接货单的时候是上传一次就掉一次接口,所以这里存在高并发的问题,第一次的代码是这个样的。

 /**
     * 批量上传接货单
     *
     * @param orderID
     * @param detailBos
     * @throws BusinessException
     */
    @Override
    public void mutilCreateReceiveOrder(Integer orderID, List<FreshCarReceivingGoodsPhotoDetailBo> detailBos) throws BusinessException {
        if (orderID == null || detailBos == null){
            throw new BusinessException("订单ID或者接货单不能为空");
        }

        //创建一张接货单审核单
        FreshCarReceivGoodsBo freshCarReceivGoodsBo = new FreshCarReceivGoodsBo();
        freshCarReceivGoodsBo.setOrderID(orderID);
        Integer recID = this.create(freshCarReceivGoodsBo);

        //填充到接货单集合中
        List<FreshCarReceivingGoodsPhotoDetailBo> list = new ArrayList<>();
        for (FreshCarReceivingGoodsPhotoDetailBo bo: detailBos){
            if (bo == null){
                continue;
            }
            bo.setReceivGoodsID(recID);
            bo.setIsDelete(false);
            bo.setCreateTime(new Date());
            list.add(bo);
        }

        freshCarReceivingGoodsPhotoDetailDomain.create(list);
    }




/**
     * 创建收货审核单
     *
     * @param freshCarReceivGoodsBo
     * @return
     * @throws BusinessException
     */
    @Override
    public Integer create(FreshCarReceivGoodsBo freshCarReceivGoodsBo) throws BusinessException {
        if (freshCarReceivGoodsBo == null){
            throw new BusinessException("收货审核单不能为空");
        }
        FreshCarReceivGoods freshCarReceivGoods = BeanUtil.convert(freshCarReceivGoodsBo,FreshCarReceivGoods.class);

        Integer orderID = freshCarReceivGoods.getOrderID();
        if (orderID == null ){
            throw new BusinessException("订单ID不能为空");
        }
        //查询该订单下是否存在收货审核单
        RedisLock lock = null;

        try {
            lock = redisUtil.initLock(RedisKeyConstant.getKey(RedisKeyConstant.Business.COMMON, RedisKeyConstant.Project.COMMON,
                    orderID.toString())); //获取锁
            if(lock.lock(1000)){
                FreshCarReceivGoodsBo  bo = this.getByOrderID(orderID);
                if (bo != null){
                    if (bo.getCheckStatus().equals(ReceiveStatusEnums.ReceiveCheckStatusEnum.WAITCHECK) ||
                            bo.getCheckStatus().equals(ReceiveStatusEnums.ReceiveCheckStatusEnum.SAVE)) {
                        return bo.getID();
                    }
                    if (bo.getCheckStatus().equals(ReceiveStatusEnums.ReceiveCheckStatusEnum.PASS)){
                        throw new BusinessException("该订单已存在审核通过的接货审核单,无法再创建");
                    }
                }
                //初始状态为保存
                freshCarReceivGoods.setCheckStatus(ReceiveStatusEnums.ReceiveCheckStatusEnum.SAVE.getValue());
                freshCarReceivGoods.setCreateTime(new Date());
                freshCarReceivGoods.setIsDelete(false);

                //保存
                return freshCarReceivGoodsService.save(freshCarReceivGoods);

            }else{
                throw new BusinessException("get lock fail");
            }
        } finally {
            if (null != lock) {
                lock.unlock();
            }
        }
这样写的话,发现后面还是会一次上传还是会创建两张相同时间的接货单审核单。

问题分析:

     这个代码的锁的范围只是在创建接货单审核单的地方,而且而且创建借货单审核单,和保存图片是在同一个domain里面,所以是在同一个事务下面,当两个线程同时进来的时候,一个线程被挡住,一个进程进去创建了审核单,但是这个时候事务并没有提交,等第二个线程进来的时候,根本在数据库里面查不到记录,所以又创建了一遍,所以后台才会出现有两个审核单的情况。

问题解决:

最后我们是这样解决的,我们将锁的范围扩大,将创建借货单审核单和保存接货单信息放在同一个锁里面,而且是在同一个事务下面,这个的缺点是相应时间会长,但是能避免创建两个接货单审核单的情况。

代码如下:

/**
     * 批量上传接货单
     *
     * @param orderID
     * @param detailBos
     * @throws BusinessException
     */
    @Override
    public void mutilCreateReceiveOrder(Integer orderID, List<FreshCarReceivingGoodsPhotoDetailBo> detailBos) throws BusinessException {
        if (orderID == null || detailBos == null){
            throw new BusinessException("订单ID或者接货单不能为空");
        }

        RedisLock lock = null;

        try {
        lock = redisUtil.initLock(RedisKeyConstant.getKey(RedisKeyConstant.Business.COMMON, RedisKeyConstant.Project.COMMON,
        "freshCar_order_"+orderID.toString()));
        if(lock.lock(2000)){
            //创建一张接货单审核单
            FreshCarReceivGoodsBo freshCarReceivGoodsBo = new FreshCarReceivGoodsBo();
            freshCarReceivGoodsBo.setOrderID(orderID);
            Integer recID = this.create(freshCarReceivGoodsBo);

            //填充到接货单集合中
            List<FreshCarReceivingGoodsPhotoDetailBo> list = new ArrayList<>();
            for (FreshCarReceivingGoodsPhotoDetailBo bo: detailBos){
                if (bo == null){
                    continue;
                }
                bo.setReceivGoodsID(recID);
                bo.setIsDelete(false);
                bo.setCreateTime(new Date());
                list.add(bo);
            }

            freshCarReceivingGoodsPhotoDetailDomain.create(list);
        }else{
            throw new BusinessException("get lock fail");
        }
        } finally {
            if (null != lock) {
                lock.unlock();
            }
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值