redis锁失败了?千万不要在事务内加锁

今天线上出现了一个故障,用户充值成功后,账户的充值记录一直没有变更.
第一个反应,就是支付公司没有回调,但是通过查询日志发现,回调时有的,只是在处理时,使用乐观锁更新账户余额失败了!
WHAT? 我不是加了分布式锁吗,为什么还会出现这个问题.

伪代码如下

@Transactional(rollbackFor = Exception.class)
    public void add(Param param) {
        // 获取redis 锁
        boolean lock = redisLock.getLock();
        if(!lock){
            throw new BaseException("获取锁失败");
        }
        // 获取账号信息
        UserAccount userAccount = getUserAccount(param.userId);

        // 更新
        userAccount.setBalance = (userAccount.getBalance + param.amount);
        userAccount.setDataVersion(userAccount.getDataVersion()+1);

        // 乐观锁更新
        if(!update(userAccount)){
            throw new BaseException("乐观锁更新失败");
        }

        // 解锁
        redisLock.unlock();
    }

通过日志发现,是在乐观锁更新时失败了,不可能呀,我明明先获取的锁,再去获取的账户信息,为什么还会更新失败呢?

最终经过脑洞思考和排查,发现了问题,详细的可以看下面的流程图.

image.png

问题的原因就是在没有提交事务的时候,提前释放了锁,导致其余的线程获取到的是同一个乐观锁版本的数据,最终更新时,导致乐观锁失败.

解决的办法,也很简单,可以将账户操作放在一个新的类方法里面,在这里进行事务操作.同时在原来的方法里面加锁调用.这样就可以解决了.

class A{
        
        public void addAndLock(Param param){
            // 获取redis 锁
            boolean lock = redisLock.getLock();
            // 执行操作
            B.add(param);
            // 解锁
            redisLock.unlock();
        }
    }
  

    class B{
        @Transactional(rollbackFor = Exception.class)
        public void add(Param param) {

            if(!lock){
                throw new BaseException("获取锁失败");
            }
            // 获取账号信息
            UserAccount userAccount = getUserAccount(param.userId);

            // 更新
            userAccount.setBalance = (userAccount.getBalance + param.amount);
            userAccount.setDataVersion(userAccount.getDataVersion()+1);

            // 乐观锁更新
            if(!update(userAccount)){
                throw new BaseException("乐观锁更新失败");
            }

        }
    }

你可能感兴趣的:(redis锁失败了?千万不要在事务内加锁)