Redission分布式锁失效问题

转载自:
原文地址-Redission分布式锁失效问题

一、锁
锁是多线程编程中的一种机制,用于控制对共享资源的访问。它可以防止多个线程同时修改或读取共享资源,从而保证线程安全。

二、分布式锁
线程锁和进程锁仅能满足在单机jvm或者同一个操作系统下,才能有效。跨jvm系统,无法满足。因此就产生了分布式锁,完成锁的工作。分布式锁一般是基于数据库,基于redis(redission),基于zookeeper三种方式实现。

三、这里我们主要说Redission分布式锁失效的问题

失效场景:在事务中使用redission分布式锁(且用户表和流水表中没有添加合适的唯一索引,或者用户余额的计算放在业务中进行计算)

场景描述:用户余额处理,要求同一笔订单不允许重复处理余额

异常代码实现 :

@Transactional(rollbackFor = Exception.class)
public void demo(Form  form){
    RLock lock = null;
    try {
        lock = redissonClient.getLock("demo_" + form.getUserUuid());
        boolean hasLock = lock.tryLock(10, TimeUnit.SECONDS);
        if(!hasLock){
            throw new BusinessException( "处理用户余额失败,此用户修改余额获取锁失败");
        }
        Integer orderCount = orderMapper.selectOrderNoRecordsCount(form.getUserUuid(),form.getOrderNo());
        if(orderCount != null && orderCount > 0){
            throw new BusinessException( "处理用户余额失败,此订单已处理");
        }
        //todo--处理用户余额

        form.setBalance(beforBalance + consumptionAmount);
        userMapper.updateUserBalance(form);

        //添加订单流水记录
        addOrderNoRecords(form);

        //todo--添加订单流水记录
        orderMapper.addOrderNoRecords(form);
        //todo--其他业务逻辑
    } finally {
        if(lock != null){
            lock.unlock();
        }
    }
}

实现的代码,看起来貌似没有问题,但是两个并发的请求req1,req2(同一个用户的余额处理)过来时,req1,req2同时查询流水表中是否存在当前订单,因为req1的事务还没有提交,req2此时查询到的orderCount也是0,那么这种情况下两个请求都会继续执行。这时可能会出现以下几种异常情况:

  1. req1,req2各自更新自己的余额,因为在业务中处理余额,会导致余额为后提交的余额,从而导致余额异常。

  2. Req1,req2如果是同一笔订单,重复提交的请求,这时因为流水表中没有添加唯一索引,这时就会出现流水表出现重复的记录问题

如果你的业务逻辑还相对复杂一些,那么还有可能引起其他异常问题。

解决方法:在使用redission分布式锁的前提下,因为我们需要保证数据的一致性,所以事务不能去除,所以我们可以直接将redission的分布式锁提到controller层,这样就可以解决了。

@PostMapping("demo")
public R demo(@RequestBody Form form){
    RLock lock = null;
    try {
        lock = redissonClient.getLock("demo_" + form.getUserUuid());
        boolean hasLock = lock.tryLock(10, TimeUnit.SECONDS);
        if(!hasLock){
            throw new BusinessException( "处理用户余额失败,此用户修改余额获取锁失败");
        }
        String result = DemoService.demo(form);
        //todo--其他业务逻辑处理
    } catch (Exception e) {
        System.out.println("打印异常信息");
    } finally {
        if(lock != null){
            lock.unlock();
        }
    }
    //todo--其他业务逻辑处理
    return R.failed("处理用户余额异常");
}
Service层:

@Transactional(rollbackFor = Exception.class)
public void demo(RequestForm  form){
    Integer orderCount = orderMapper.selectOrderNoRecordsCount(form.getUserUuid(),form.getOrderNo());
    if(orderCount != null && orderCount > 0){
        throw new BusinessException( "处理用户余额失败,此订单已处理");
    }
    //todo--处理用户余额
    form.setBalance(beforBalance + consumptionAmount);
    userMapper.updateUserBalance(form);

    //添加订单流水记录
    addOrderNoRecords(form);

    //todo--添加订单流水记录
    orderMapper.addOrderNoRecords(form);
    //todo--其他业务逻辑
}
  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值