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 集群伸缩原理
May 15 Redis
浅谈Redis的几个过期策略
May 27 Redis
Redis Cluster集群动态扩容的实现
Jul 15 Redis
嵌入式Redis服务器在Spring Boot测试中的使用教程
Jul 21 Redis
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Feb 12 Redis
解决Redis启动警告问题
Feb 24 Redis
redis数据结构之压缩列表
Mar 21 Redis
sentinel支持的redis高可用集群配置详解
Apr 01 Redis
Redis中key的过期删除策略和内存淘汰机制
Apr 12 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 Redis
Redis配置外网可访问(redis远程连接不上)的方法
Dec 24 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
在windows iis5下安装php4.0+mysql之我见
2006/10/09 PHP
PHP的分页功能
2007/03/21 PHP
使用cookie实现统计访问者登陆次数
2013/06/08 PHP
PHP扩展Memcache分布式部署方案
2015/12/06 PHP
PHP更安全的密码加密机制Bcrypt详解
2017/06/18 PHP
Javascript倒计时代码
2010/08/12 Javascript
iframe子页面获取父页面元素的方法
2013/11/05 Javascript
jquery取子节点及当前节点属性值的方法
2014/09/09 Javascript
JQuery+EasyUI轻松实现步骤条效果
2016/02/22 Javascript
Bootstrap按钮下拉菜单组件详解
2016/05/10 Javascript
js实现九宫格的随机颜色跳转
2017/02/19 Javascript
JavaScript实现购物车基本功能
2017/07/21 Javascript
详解Vue Elememt-UI构建管理后台
2018/02/27 Javascript
Vue-cli项目获取本地json文件数据的实例
2018/03/07 Javascript
vue-cli 3.x 配置Axios(proxyTable)跨域代理方法
2018/09/19 Javascript
vue.js自定义组件directives的实例代码
2018/11/09 Javascript
JavaScript遍历数组和对象的元素简单操作示例
2019/07/09 Javascript
使用python实现baidu hi自动登录的代码
2013/02/10 Python
使用python获取CPU和内存信息的思路与实现(linux系统)
2014/01/03 Python
Python2中的raw_input() 与 input()
2015/06/12 Python
Python回文字符串及回文数字判定功能示例
2018/03/20 Python
浅谈Python Opencv中gamma变换的使用详解
2018/04/02 Python
Python DataFrame 设置输出不显示index(索引)值的方法
2018/06/07 Python
python画双y轴图像的示例代码
2019/07/07 Python
python根据多个文件名批量查找文件
2019/08/13 Python
ET Mall东森购物网:东森严选
2017/03/06 全球购物
prAna官网:瑜伽、旅行和冒险服装
2019/03/10 全球购物
StubHub美国:购买或出售您的门票
2019/07/09 全球购物
AP澳洲中文网:澳洲正品直邮,包税收件无忧
2019/07/12 全球购物
说一下mysql, oracle等常见数据库的分页实现方案
2012/09/29 面试题
求职者应聘的自我评价
2013/10/16 职场文书
关于运动会的稿件
2014/02/02 职场文书
党员学习正风肃纪思想汇报
2014/09/12 职场文书
商场营业员岗位职责
2015/04/14 职场文书
黄埔军校观后感
2015/06/10 职场文书
SpringBoot 集成短信和邮件 以阿里云短信服务为例
2022/04/22 Java/Android