redis分布式锁的五个坑

本文探讨了Redis分布式锁在高并发场景中的重要性,并列举了几个常见的问题及解决方案,包括锁未被释放、锁被错误释放、数据库事务超时、锁过期与业务执行同步问题以及主从复制带来的风险。通过使用Redisson客户端和正确处理事务与锁的生命周期,可以有效避免这些问题,确保分布式锁的安全使用。
摘要由CSDN通过智能技术生成

redis分布式锁在实际开发过程中的重要性,不言而喻,设计高并发的业务场景,redis几乎都会登场。

今天给大家分享一下redis分布式锁的几大坑吧😂

1、锁未被释放

请看下面一段代码

1    /**
 2     * @author fu JC
 3     * @description 扣减库存
 4     * @date 2020/4/21 12:10
 5     */
 6   public String stockLock() {
 7        RLock lock = redissonClient.getLock("stockLock");
 8        try {
 9            /**
10             * 获取锁
11             */
12            if (lock.tryLock(10, TimeUnit.SECONDS)) {
13                /**
14                 * 查询库存数
15                 */
16                Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stockCount"));
17                /**
18                 * 扣减库存
19                 */
20                if (stock > 0) {
21                    stock = stock - 1;
22                    stringRedisTemplate.opsForValue().set("stockCount", stock.toString());
23                    LOGGER.info("库存扣减成功,剩余库存数量:{}", stock);
24                } else {
25                    LOGGER.info("库存不足~");
26                }
27            } else {
28                LOGGER.info("未获取到锁业务结束..");
29            }
30        } catch (Exception e) {
31            LOGGER.info("处理异常", e);
32        } 
35        return "ok";
36  }

这是正常的商场扣减库存的redis分布式锁,方式超卖现象的发生。但细心的朋友们就会看到,在结尾时未正常释放锁,导致redis线程池被打满,redis服务大面积故障,可能会造成库存数据扣减混乱。

解决的方法也很简单,只要我们细心一点,拿到锁的线程处理完业务及时释放锁,如果是重入锁未拿到锁后,线程可以释放当前连接并且sleep一段时间。

2、B的锁被A释放了

redis实现锁的原理是setnx命令,当 key不存在时将 key的值设为 value ,返回值为 1;若给定的 key已经存在,则 SETNX不做任何动作,返回值为 0 。

1 SETNX key value

但是来假设一个场景,当A、B两个线程尝试给key 加锁的时候,A先拿到锁(假设锁3秒后过期),然后B在外面尝试拿锁。~到这,其实一点问题都没有

但此时业务逻辑比较耗时,执行时间已经超过redis锁的过期时间,这个时候A就会自动释放锁,然后B线程检查到key不存在,执行setnx命令就也拿到了锁。

当A执行完业务逻辑之后,就会去释放锁,这就导致了B的锁被A给释放了。

解决办法其实很简单,一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,就可以避免上面的情况。

3、数据库事务超时

看下面这段代码:

 1   @Transaction
 2   public void lock() {
 3
 4        while (true) {
 5            boolean flag = this.getLock(key);
 6            if (flag) {
 7                insert();
 8            }
 9        }
10    }

这个方法上面有一个@Transaction注解,当有异常时,就会自动回滚。但是数据库事务是有超时时间限制的,不会无条件的一直等下去。
当我们解析一个大文件1,并且把他写入数据库的时候,如果执行时间太长,就会造成超时,事务就会回滚。
一旦你的key长时间获取不到锁,获取锁等待的时间远超过数据库事务超时时间,程序就会报异常。
这个时候,我们就需要将数据库事务改为手动提交、回滚事务。

 1    @Autowired
 2    DataSourceTransactionManager dataSourceTransactionManager;
 3
 4    @Transaction
 5    public void lock() {
 6        //手动开启事务
 7        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
 8        try {
 9            while (true) {
10                boolean flag = this.getLock(key);
11                if (flag) {
12                    insert();
13                    //手动提交事务
14                    dataSourceTransactionManager.commit(transactionStatus);
15                }
16            }
17        } catch (Exception e) {
18            //手动回滚事务
19            dataSourceTransactionManager.rollback(transactionStatus);
20        }
21    }

4、锁过期了,业务还没有执行完

这种情况和上面的第二种情况比较相似,但解决思路有点不一样。

同样是redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?

那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响操作性能。

要是redis锁的过期时间能够自动续期就好了。

为了解决这个问题我们使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。

redisson对分布式锁做了很好封装,只需调用API即可。

1  RLock lock = redissonClient.getLock("stockLock");

redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。

举例子:假如加锁的时间是30秒,过10秒检查一次,一旦加锁的业务没有执行完,就会进行一次续期,把锁的过期时间再次重置成30秒。

5、redis主从复制的坑

redis高可用最常见的方案就是主从复制(master-slave),这种模式也给redis分布式锁挖了一坑。

redis cluster集群环境下,假如现在A客户端想要加锁,它会根据路由规则选择一台master节点写入key mylock,在加锁成功后,master节点会把key异步复制给对应的slave节点。

如果此时redis master节点宕机,为保证集群可用性,会进行主备切换,slave变为了redis master。B客户端在新的master节点上加锁成功,而A客户端也以为自己还是成功加了锁的。

此时就会导致同一时间内多个客户端对一个分布式锁完成了加锁,导致各种脏数据的产生。

至于解决办法嘛,目前看还没有什么根治的方法,只能尽量保证机器的稳定性,减少发生此事件的概率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值