天天看點

工作中redis鎖的問題記錄

受到公司剛大佬的影響,說了很多關于寫總結的好處,于是我就試着寫一寫

工作中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();
            }
        }
    }
           

繼續閱讀