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 Cluster 字段模糊匹配及删除
May 27 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
你真的了解redis为什么要提供pipeline功能
Jun 22 Redis
Redis做数据持久化的解决方案及底层原理
Jul 15 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
聊聊redis-dump工具安装问题
Jan 18 Redis
Redis分布式锁的7种实现
Apr 01 Redis
Redis实现一个账号只能登录一个设备
Apr 19 Redis
解决 redis 无法远程连接
May 15 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实现图片简单上传
2006/10/09 PHP
学习js所必须要知道的一些
2007/03/07 Javascript
JavaScript 弹出窗体点击按钮返回选择数据的实现
2010/04/01 Javascript
ie6下png图片背景不透明的解决办法使用js实现
2013/01/11 Javascript
JS辨别访问浏览器判断是android还是ios系统
2014/08/19 Javascript
jquery中EasyUI使用技巧小结
2015/02/10 Javascript
jquery滚动特效集锦
2015/06/03 Javascript
理解javascript中的with关键字
2016/02/15 Javascript
基于Jquery插件实现跨域异步上传文件功能
2016/04/26 Javascript
微信小程序 用户数据解密详细介绍
2017/01/09 Javascript
微信小程序之MaterialDesign--input组件详解
2017/02/15 Javascript
Angular2 父子组件通信方式的示例
2018/01/29 Javascript
vue中使用ueditor富文本编辑器
2018/02/08 Javascript
jQuery中可见性过滤器简单用法示例
2018/03/31 jQuery
webpack打包多页面的方法
2018/11/30 Javascript
解决Vue调用springboot接口403跨域问题
2019/09/02 Javascript
[06:30]DOTA2英雄梦之声_第15期_死亡先知
2014/06/21 DOTA
[32:17]完美世界DOTA2联赛循环赛LBZS vs Forest第二场 10月30日
2020/10/31 DOTA
python获取程序执行文件路径的方法(推荐)
2018/04/26 Python
Python socket 套接字实现通信详解
2019/08/27 Python
PyCharm更改字体和界面样式的方法步骤
2019/09/27 Python
Python 用三行代码提取PDF表格数据
2019/10/13 Python
Python 线性回归分析以及评价指标详解
2020/04/02 Python
Python二元算术运算常用方法解析
2020/09/15 Python
CSS3实现的文本3D效果附图
2014/09/03 HTML / CSS
中国跨境在线时尚零售商:Bellelily
2018/04/06 全球购物
半年思想汇报
2013/12/30 职场文书
校园活动策划书范文
2014/01/10 职场文书
30岁生日感言
2014/01/25 职场文书
社区国庆节活动方案
2014/02/05 职场文书
我爱家乡演讲稿
2014/09/12 职场文书
办理房产过户的委托书
2014/09/14 职场文书
医院护士工作检讨书
2014/10/26 职场文书
六一晚会主持词开场白
2015/05/28 职场文书
办公室日常管理制度
2015/08/04 职场文书
python cv2图像质量压缩的算法示例
2021/06/04 Python