Redisson实现Redis分布式锁的几种方式


Posted in Redis onAugust 07, 2021

前几天发的一篇文章《Redlock:Redis分布式锁最牛逼的实现》,引起了一些同学的讨论,也有一些同学提出了一些疑问,这是好事儿。本文在讲解如何使用Redisson实现Redis普通分布式锁,以及Redlock算法分布式锁的几种方式的同时,也附带解答这些同学的一些疑问。

Redis几种架构

Redis发展到现在,几种常见的部署架构有:

  • 单机模式;
  • 主从模式;
  • 哨兵模式;
  • 集群模式;

我们首先基于这些架构讲解Redisson普通分布式锁实现,需要注意的是,只有充分了解普通分布式锁是如何实现的,才能更好的了解Redlock分布式锁的实现,因为Redlock分布式锁的实现完全基于普通分布式锁。

普通分布式锁

Redis普通分布式锁这个大家基本上只了解,本文不打算过多的介绍,上一篇文章《Redlock:Redis分布式锁最牛逼的实现》也讲的很细,并且也说到了几个重要的注意点。

所以直接show you the code,毕竟talk is cheap。

redisson版本

本次测试选择redisson 2.14.1版本。

单机模式

源码如下:

// 构造redisson实现分布式锁必要的Config
Config config = new Config();
config.useSingleServer().setAddress("redis://172.29.1.180:5379").setPassword("a123456").setDatabase(0);
// 构造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 设置锁定资源名称
RLock disLock = redissonClient.getLock("DISLOCK");
boolean isLock;
try {
    //尝试获取分布式锁
    isLock = disLock.tryLock(500, 15000, TimeUnit.MILLISECONDS);
    if (isLock) {
        //TODO if get lock success, do something;
        Thread.sleep(15000);
    }
} catch (Exception e) {
} finally {
    // 无论如何, 最后都要解锁
    disLock.unlock();
}

通过代码可知,经过Redisson的封装,实现Redis分布式锁非常方便,我们再看一下Redis中的value是啥,和前文分析一样,hash结构,key就是资源名称,field就是UUID+threadId,value就是重入值,在分布式锁时,这个值为1(Redisson还可以实现重入锁,那么这个值就取决于重入次数了):

172.29.1.180:5379> hgetall DISLOCK
1) "01a6d806-d282-4715-9bec-f51b9aa98110:1"
2) "1"

哨兵模式

即sentinel模式,实现代码和单机模式几乎一样,唯一的不同就是Config的构造:

Config config = new Config();
config.useSentinelServers().addSentinelAddress(
        "redis://172.29.3.245:26378","redis://172.29.3.245:26379", "redis://172.29.3.245:26380")
        .setMasterName("mymaster")
        .setPassword("a123456").setDatabase(0);

集群模式

集群模式构造Config如下:

Config config = new Config();
config.useClusterServers().addNodeAddress(
        "redis://172.29.3.245:6375","redis://172.29.3.245:6376", "redis://172.29.3.245:6377",
        "redis://172.29.3.245:6378","redis://172.29.3.245:6379", "redis://172.29.3.245:6380")
        .setPassword("a123456").setScanInterval(5000);

总结

普通分布式实现非常简单,无论是那种架构,向Redis通过EVAL命令执行LUA脚本即可。

Redlock分布式锁

那么Redlock分布式锁如何实现呢?以单机模式Redis架构为例,直接看实现代码:

Config config1 = new Config();
config1.useSingleServer().setAddress("redis://172.29.1.180:5378")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();
config2.useSingleServer().setAddress("redis://172.29.1.180:5379")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();
config3.useSingleServer().setAddress("redis://172.29.1.180:5380")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);

String resourceName = "REDLOCK";
RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;
try {
    isLock = redLock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
    System.out.println("isLock = "+isLock);
    if (isLock) {
        //TODO if get lock success, do something;
        Thread.sleep(30000);
    }
} catch (Exception e) {
} finally {
    // 无论如何, 最后都要解锁
    System.out.println("");
    redLock.unlock();
}

最核心的变化就是RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);,因为我这里是以三个节点为例。

那么如果是哨兵模式呢?需要搭建3个,或者5个sentinel模式集群(具体多少个,取决于你)。
那么如果是集群模式呢?需要搭建3个,或者5个cluster模式集群(具体多少个,取决于你)。

实现原理

既然核心变化是使用了RedissonRedLock,那么我们看一下它的源码有什么不同。这个类是RedissonMultiLock的子类,所以调用tryLock方法时,事实上调用了RedissonMultiLock的tryLock方法,精简源码如下:

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    // 实现要点之允许加锁失败节点限制
    int failedLocksLimit = failedLocksLimit();
    List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
    // 实现要点之遍历所有节点通过EVAL命令执行lua加锁
    for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
        RLock lock = iterator.next();
        boolean lockAcquired;
        try {
            // 对节点尝试加锁
            lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
        } catch (RedisConnectionClosedException|RedisResponseTimeoutException e) {
            // 如果抛出这类异常,为了防止加锁成功,但是响应失败,需要解锁
            unlockInner(Arrays.asList(lock));
            lockAcquired = false;
        } catch (Exception e) {
            // 抛出异常表示获取锁失败
            lockAcquired = false;
        }
        
        if (lockAcquired) {
            // 成功获取锁集合
            acquiredLocks.add(lock);
        } else {
            // 如果达到了允许加锁失败节点限制,那么break,即此次Redlock加锁失败
            if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                break;
            }               
        }
    }
    return true;
}

很明显,这段源码就是上一篇文章《Redlock:Redis分布式锁最牛逼的实现》提到的Redlock算法的完全实现。

以sentinel模式架构为例,如下图所示,有sentinel-1,sentinel-2,sentinel-3总计3个sentinel模式集群,如果要获取分布式锁,那么需要向这3个sentinel集群通过EVAL命令执行LUA脚本,需要3/2+1=2,即至少2个sentinel集群响应成功,才算成功的以Redlock算法获取到分布式锁:

Redisson实现Redis分布式锁的几种方式

问题合集

Redisson实现Redis分布式锁的几种方式

根据上面实现原理的分析,这位同学应该是对Redlock算法实现有一点点误解,假设我们用5个节点实现Redlock算法的分布式锁。那么要么是5个redis单实例,要么是5个sentinel集群,要么是5个cluster集群。而不是一个有5个主节点的cluster集群,然后向每个节点通过EVAL命令执行LUA脚本尝试获取分布式锁,如上图所示。

失效时间如何设置
这个问题的场景是,假设设置失效时间10秒,如果由于某些原因导致10秒还没执行完任务,这时候锁自动失效,导致其他线程也会拿到分布式锁。

这确实是Redis分布式最大的问题,不管是普通分布式锁,还是Redlock算法分布式锁,都没有解决这个问题。也有一些文章提出了对失效时间续租,即延长失效时间,很明显这又提升了分布式锁的复杂度。另外就笔者了解,没有现成的框架有实现,如果有哪位知道,可以告诉我,万分感谢。

redis分布式锁的高可用
关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间已经发生过一场争论。有兴趣的同学,搜索"基于Redis的分布式锁到底安全吗"就能得到你想要的答案,需要注意的是,有上下两篇(这应该就是传说中的神仙打架吧,哈)。

zookeeper or redis
没有绝对的好坏,只有更适合自己的业务。就性能而言,redis很明显优于zookeeper;就分布式锁实现的健壮性而言,zookeeper很明显优于redis。如何选择,取决于你的业务!

到此这篇关于Redisson实现Redis分布式锁的几种方式的文章就介绍到这了,更多相关Redisson实现Redis分布式锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
redis客户端实现高可用读写分离的方式详解
Jul 04 Redis
基于Redis结合SpringBoot的秒杀案例详解
Oct 05 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
Redis高并发防止秒杀超卖实战源码解决方案
Nov 01 Redis
关于使用Redisson订阅数问题
Jan 18 Redis
redis数据一致性的实现示例
Mar 18 Redis
解决 redis 无法远程连接
May 15 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
Redis基本数据类型Set常用操作命令
Jun 01 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
使用Redis实现分布式锁的方法
Jun 16 Redis
Redis唯一ID生成器的实现
Jul 07 Redis
Redis分布式锁Redlock的实现
Aug 07 #Redis
关于redisson缓存序列化几枚大坑说明
Aug 04 #Redis
Redis Cluster 集群搭建你会吗
Aug 04 #Redis
解析redis hash应用场景和常用命令
Aug 04 #Redis
redis 存储对象的方法对比分析
Aug 02 #Redis
springboot使用Redis作缓存使用入门教程
Jul 25 #Redis
Redis中一个String类型引发的惨案
You might like
简单的php 验证图片生成函数
2009/05/21 PHP
php 使用post,get的一种简洁方式
2010/04/25 PHP
学习php开源项目的源码指南
2014/12/21 PHP
PHP中实现crontab代码分享
2015/03/26 PHP
php实现图片上传时添加文字和图片水印技巧
2020/04/18 PHP
PHP如何防止XSS攻击与XSS攻击原理的讲解
2019/03/22 PHP
JavaScript中使用replace结合正则实现replaceAll的效果
2010/06/04 Javascript
返回对象在当前级别中是第几个元素的实现代码
2011/01/20 Javascript
document.getElementById介绍
2011/09/13 Javascript
jQuery语法总结和注意事项小结
2012/11/11 Javascript
thinkphp 表名 大小写 窍门
2015/02/01 Javascript
jQuery+html5实现div弹出层并遮罩背景
2015/04/15 Javascript
基于Jquery实现表单验证
2020/07/20 Javascript
JS遍历对象属性的方法示例
2017/01/10 Javascript
Web制作验证码功能实例代码
2017/06/19 Javascript
基于构造函数的五种继承方法小结
2017/07/27 Javascript
如何理解Vue的.sync修饰符的使用
2017/08/17 Javascript
详解webpack-dev-server的简单使用
2018/04/02 Javascript
JS获取子节点、父节点和兄弟节点的方法实例总结
2018/07/06 Javascript
Layui 设置select下拉框自动选中某项的方法
2018/08/14 Javascript
JavaScript解析及序列化JSON的方法实例分析
2019/01/04 Javascript
vue组件开发props验证的实现
2019/02/12 Javascript
JavaScript实现简易计算器小功能
2020/10/22 Javascript
[01:06:07]2014 DOTA2国际邀请赛中国区预选赛5.21 DT VS CIS
2014/05/22 DOTA
[47:53]DOTA2上海特级锦标赛主赛事日 - 1 败者组第一轮#2COL VS Spirit
2016/03/02 DOTA
Python第三方库xlrd/xlwt的安装与读写Excel表格
2017/01/21 Python
Python之批量创建文件的实例讲解
2018/05/10 Python
Python3字符串encode与decode的讲解
2019/04/02 Python
Python动态语言与鸭子类型详解
2019/07/01 Python
RetroStage德国:复古服装
2019/02/03 全球购物
小区门卫的岗位职责
2014/09/26 职场文书
领导干部个人整改措施落实情况汇报
2014/10/29 职场文书
2015年保安个人工作总结
2015/04/02 职场文书
Python使用protobuf序列化和反序列化的实现
2021/05/19 Python
React-vscode使用jsx语法的问题及解决方法
2021/06/21 Javascript
JavaWeb 入门篇(3)ServletContext 详解 具体应用
2021/07/16 Java/Android