redisson实现的MultiLock、RedLock分布式锁使用场景和可能存在的问题。

目录

一、redisson简介

二、联锁MultiLock和红锁RedLock

三、这两种锁可能会出现的功能缺陷

四、问题的解决方案——个人设想


一、redisson简介

        Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它是一个基于Redis实现的高级分布式锁客户端。同时Redisson是Redis官网指定的RedlLock使用Java语言的实现。

        网络上沿着Redisson的官方文档的介绍已经很多,公平锁、读写锁、信号量、闭锁的用法和源码解析。我这里就不做赘述。本篇博文主要讲使用联锁MultiLock和红锁RedLock的必要场景,以及可能出现的问题

二、联锁MultiLock和红锁RedLock

        因为Redis是CAP中AP架构设计,主打的就是牺牲出现可能性比较小的一致性,达到高可用,高性能的目的。RedLock算法的出现就是为了解决redis实现的分布式锁的不能保证强数据一致性的问题,即便如此,RedLock也只是尽量将数据不一致的可能性降到最低,并不能完全解决这个问题。下面就重点探讨RedLock也可能出现的问题。

        因为RedissonRedLock是继承自RedissonMultiLock的,只是重写了failedLocksLimit方法,即允许锁获取失败个数,获取锁的等待时间等方法。总体来说还是paxos过半机制那一套。所以先说RedissonMultiLock吧。

        所谓联锁,其实就是将多个RLock形成一个锁组合,遍历组合内各个key,分别去获取锁,但是它允许获取失败的锁(Key)为0。具体代码如下:

 protected int failedLocksLimit() {
        return 0;
    }

        这种锁适合资源严格互斥的需要分布式锁加持的方法,也就是适合业务中,可能会需要更新数据的每个key,被严格保护,即使是其他的方法中,需要使其中的一个Key,此时也只能互斥的不能获取资源。在我的上一篇博文中举例:场景(1):一个订单中有多种商品,提交订单的时候,每种商品的库存需要被扣除。这种场景就需要用MultiLock,而不是RedLock。但是如果redis是单机模式,主从模式部署,服务器宕机怎么办呢?没有备份实例,或者备份实例不能自动替换,没有灾备方案。

        那么就用哨兵或者集群模式部署可以解决吗?这两种模式都具备故障转移的功能,但是在转移的过程中,会出现脑裂的问题。也就是多个key中某一个key映射到的某台实例上的数据在故障转译中丢失了一部分,正好这个key的缓存数据在节点故障恢复后丢失了,对应的锁也不存在了。

        RedLock就是为此设计的,即使多个Key中少部分key因为故障,数据丢失,失去了锁,但组合锁总体仍然具备分布式锁功能。具体就是重写RedissonMultiLock的两个方法。

 public RedissonRedLock(RLock... locks) {
        super(locks);
    }

    @Override
    protected int failedLocksLimit() {
        return locks.size() - minLocksAmount(locks);
    }
    
    protected int minLocksAmount(final List<RLock> locks) {
        return locks.size()/2 + 1;
    }

        从源码可以看到,相对于RedissonMultiLock,它的改动并不大,仍然是根据CRC16 hash映射到不同的节点,所以RedLock的key,如果完全选取业务相关的互斥资源的key,在获取分布式锁的时候,需要获取redis主节点服务实例的个数,可能是偶数,也可能是不大于3的奇数。这样不能实现过半算法。

       

三、这两种锁可能会出现的功能缺陷

        假如主节点Master有5个,如果key选取的不合适,5个key都映射到同一个主节点上,这个主节点出现故障转移,那不就跟单机模式一样吗?但是如果完全选取与业务不相干的key,每次请求的使用的key都一样,岂不是不相干不互斥的请求(举例:前面一个订单需要商品A,紧接着接受到一个需要商品B的订单提交请求),也要争用同一个锁吗?而每次key随机选取不一样,可能需要互斥争用锁的请求,变成不互斥的业务请求了,这就失去了分布式锁的本来目的,本末倒置。

        

        很多人认为,要实现RedLock锁,至少需要5台redis Master实例,因为官方推荐?官方这里只是举例,并不是推荐至少需要5个主节点实例。我想应该是翻译的问题。

        看源码,和官方说明,也就是至少是3个节点而已。

        那RedLock的选用场景是什么样的呢?或者说RedLock的lockKey设置,有做补偿方案吗?

        首先能选取RedLock的方案就代表可以容忍部分数据不一致,比如

场景(2):线程1,请求参数中有A、B、C三个key,而线程2请求参数只有C(或者 C、E、F),假如线程1中的C刚好因为故障转移失效了,线程2获取锁成功,同时修改C相关的资源。(或者线程2只有C获取失败,整体获得分布式锁)这不就改变了保证数据一致性,要达到互斥的初衷了吗?

        由此可见,RedLock的局限性也非常大,可能只适合分布式部署,参数固定请求方法吧。旨在减小故障转移带来的同时获取到锁的可能性罢了。这也是我之前的项目,没有选取RedLock,而是选用MultiLock的原因。

        如果非要做一个补偿方案不可,那就是目前的Redisson源码要重写,不论lockKey有多少个,所有的key需要在所有的master节点上创建内存数据,但是redis主节点存储数据是只能分片,不能冗余。所以需要加前后缀,相同前后缀规则就会得到相同互斥key。比如key test在A、B、C节点上的数据就是testA、testB、testC,哪怕只有一个key,即可实现过半机制,从而在算法功能和业务功能上都可以将RedLock适用场景拓展开。也算是修补算法实现的不足。(算法本身没问题,算法实现的不太好)

        我再此主要列举了业务上可能出现冲突的问题,至于RedLock因为环境因素的可能导致的问题,网络上好多人总结过了,我就不赘述,但还是引用一下:

问题1: 宕机重启之后,2个客户端拿到同一把锁。

假设5个节点是A, B, C, D, E,客户端1在A, B, C上面拿到锁,D, E没有拿到锁,客户端1拿锁成功。 此时,C挂了重启,C上面锁的数据丢失(假设机器断电,数据还没来得及刷盘;或者C上面的主节点挂了,从节点未同步)。客户端2去取锁,从C, D, E 3个节点拿到锁,A, B没有拿到(还被客户端1持有),客户端2也超过多数派,也会拿到锁。

解决方案- 延迟重启;但是由于时钟跳变的因素,导致延迟重启时效(无法解决该问题);

问题2:脑裂问题:就是多个客户端同时竞争同一把锁,最后全部失败。

比如有节点1、2、3、4、5,A、B、C同时竞争锁,A获得1、2,B获得3、4,C获得5,最后ABC都没有成功获得锁,没有获得半数以上的锁。官方的建议是尽量同时并发的向所有节点发送获取锁命令。客户端取得大部分Redis实例锁所花费的时间越短,脑裂出现的概率就会越低。 需要强调,当客户端从大多数Redis实例获取锁失败时,应该尽快地释放(部分)已经成功取到的锁,方便别的客户端去获取锁,假如释放锁失败了,就只能等待锁超时释放了获取锁失败尽快释放,redisson源码已经实现了

问题3:效率低,主节点越多,获取锁的时间越长;

问题4: 时钟跳跃

刚上面讨论的方案严格依赖时钟,而5台机器上面的时钟是可能有误差的。

时钟跳跃的意思就是:实际时间只过了1s钟(假设),但系统里面2次时间之差可能是1分钟,也就是系统之间发生了跳跃。发生这种情况,可能是运维人员认为修改了系统时间。

时钟跳跃会产生2个后果:

(1)延迟重启机制失效。时钟跳跃可能导致机器挂了立马重启,从而出现上面的问题。

(2)时钟跳跃导致客户端拿到锁之后立马失效。endTime - beginTime 差值太大。这虽然不影响正确性,但影响拿锁的效率。

那么时钟回拨呢?endTime - beginTime会成为负值,不影响算法的正确性。

问题3: 客户端大延迟(比如full GC),2个客户端拿到同一把锁。

理论上,一切有超时强制释放机制的锁,都可能产生这个问题。服务端把锁强制释放了,但是客户端的代码并没有执行完,卡在了某个地方(比如full GC,或者其它原因导致进程暂停),这把锁被分配给了另外一个客户端。

针对这个问题,Redis又提出了watch dog机制。大致意思就是,锁快要到期之前,发现客户端业务逻辑还没执行完,就给锁续期,避免锁被强制释放,分配给另外一个客户端。但是,锁续期本身是个网络操作,也没办法保证续期一定成功!

从这个案例中,可以得到2个重要启示:

(1)在分布式系统中,严格依赖每台机器本机时钟的算法,都可能有风险。

(2)一切具有“超时强制释放机制”的锁,都可能导致客户端还在持有锁的情况下,锁被强制释放。

四、问题的解决方案——个人设想

        不管是MultiLock还是RedLock,先抛开业务功能上的缺陷问题(适用场景局限的问题),上面的问题加起来总结成一个就是:业务代码还没有执行完,锁数据直接丢失了,看门狗WatchDog续期都没法续期。

        那么我说下我自己的方案:业务计算完毕后,将要通过redisClient修改数据的时候,不要用RedisTemplate这种。而是将所有需要修改的调用,全部打包放到redis事务来执行。在提交事务之前,再次获取一下所有的key是否存在,如果有一个不存在(RedLocK有超过半数的不存在)就手动回滚所有的操作,如果伴有数据库操作,用数据库事务回滚。

        Redis事务是通过MULTI、EXEC、WATCH和UNWATCH等命令实现的。在Redisson客户端中,RTransaction对象会在调用commit()方法时生成一个MULTI命令,并将所有操作参数保存在该命令cache中。当事务执行完毕并调用commit()方法时,Redisson会向Redis服务器发送EXEC命令以执行该事务中的所有操作。如果其中任何一个操作失败,Redisson会立即回滚整个事务(加了WATCH命令)。redis事务执行失败就是报错,抛出异常给RedissonTransaction。

         // 定义事务选项
        TransactionOptions options = TransactionOptions.defaults()
                .timeout(1) // 设置事务超时时间,单位为秒,默认为60秒
                .retryAttempts(2) // 设置事务重试次数,默认为3次
                .retryInterval(100); // 设置事务重试间隔时间,单位为毫秒,默认为1000毫秒

        // 创建RTransaction对象,开始事务
        RTransaction transaction = redisson.createTransaction(options);

        try {
            // 在事务中执行一系列操作
            transaction.getBucket("key1").set("value1");
            transaction.getMap("map1").put("field1", "value2");
            transaction.getSet("set1").add("value3");

            // 提交事务
            transaction.commit();
        } catch (Exception e) {
            // 回滚事务
            transaction.rollback();
        } finally {
            // 关闭Redisson连接
            redisson.shutdown();
        }

        redis的事务的作用在此体现出来了,相比Lua脚本,执行失败会回滚前面所有操作。

        业务代码怎么实现回滚呢?这就更简单了,只需要clear掉cache里面的所有指令,并且释放还存在的key锁就行。

更多资源分享,请关注我的公众号:搜索或扫码 砥砺code

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis可以通过使用RedLock算法来实现分布式锁的延期。RedLock是基于多个Redis集群部署的高可用分布式锁解决方案。在RedLock中,如果master节点宕机期间,可以容忍多个客户端同时持有锁。因此,如果你的Redis集群是单master的,那么你需要考虑是否可以使用Redis作为分布式锁。\[2\] 具体来说,你可以使用Redisson库来实现RedLockRedisson提供了封装了可重入锁(Reentrant Lock)、公平锁(Fair Lock)、联锁(MultiLock)、红锁(RedLock)、读写锁(ReadWriteLock)、信号量(Semaphore)、可过期性信号量(PermitExpirableSemaphore)、闭锁(CountDownLatch)等多种锁的功能。你可以参考Redisson的官方文档来了解具体的使用说明。\[1\] 通过使用RedLock,你可以保证分布式锁的高可用性和数据的绝对不丢失。这对于存储锁信息非常重要,因为Redis集群是异步复制的方式,可以容忍一定范围的数据丢失,但是锁信息是不能丢失的。因此,RedLock通过多个master节点来保证锁数据的绝对不丢失。\[3\] #### 引用[.reference_title] - *1* *2* [Redis的分布式锁详解](https://blog.csdn.net/a745233700/article/details/88084219)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [分布式锁、ZK分布式锁Redis分布式锁](https://blog.csdn.net/weixin_35794878/article/details/122635171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值