Redis集群环境下的-RedLock(真分布式锁) 实践

news/2024/7/24 12:45:55

在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增加一点复杂的设计就可以获得更好的可靠性。 这篇文章的目的就是尝试提出一种官方权威的用Redis实现分布式锁管理器的算法,我们把这个算法称为RedLock。

Redlock是redis官方提出的实现分布式锁管理器的算法。这个算法会比一般的普通方法更加安全可靠。关于这个算法的讨论可以看下官方文档。

https://github.com/antirez/re...

安全和可靠性保证

在描述我们的设计之前,我们想先提出三个属性,这三个属性在我们看来,是实现高效分布式锁的基础。

1、一致性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
2、分区可容忍性:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
3、可用性:只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。

为什么基于故障切换的方案不够好

为了理解我们想要提高的到底是什么,我们先看下当前大多数基于Redis的分布式锁三方库的现状。 用Redis来实现分布式锁最简单的方式就是在实例里创建一个键值,创建出来的键值一般都是有一个超时时间的(这个是Redis自带的超时特性),所以每个锁最终都会释放。

而当一个客户端想要释放锁时,它只需要删除这个键值即可。 表面来看,这个方法似乎很管用,但是这里存在一个问题:在我们的系统架构里存在一个单点故障,如果Redis的master节点宕机了怎么办呢?有人可能会说:加一个slave节点!在master宕机时用slave就行了!但是其实这个方案明显是不可行的,因为这种方案无法保证第1个安全互斥属性,因为Redis的复制是异步的。 总的来说,这个方案里有一个明显的竞争条件(race condition),举例来说:

1、客户端A在master节点拿到了锁。
2、master节点在把A创建的key写入slave之前宕机了。
3、slave变成了master节点
4、B也得到了和A还持有的相同的锁(因为原来的slave里还没有A持有锁的信息)

当然,在某些特殊场景下,前面提到的这个方案则完全没有问题,比如在宕机期间,多个客户端允许同时都持有锁,如果你可以容忍这个问题的话,那用这个基于复制的方案就完全没有问题,否则的话我们还是建议你采用这篇文章里接下来要描述的方案。

Redlock 简介

在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。实现高效的分布式锁有三个属性需要考虑:

1、安全属性:互斥,不管什么时候,只有一个客户端持有锁
2、效率属性A:不会死锁
3、效率属性B:容错,只要大多数redis节点能够正常工作,客户端端都能获取和释放锁。

Redlock 算法

在分布式版本的算法里我们假设我们有N个Redis master节点,这些节点都是完全独立的,我们不用任何复制或者其他隐含的分布式协调算法。我们已经描述了如何在单节点环境下安全地获取和释放锁。因此我们理所当然地应当用这个方法在每个单节点里来获取和释放锁。在我们的例子里面我们把N设成5,这个数字是一个相对比较合理的数值,因此我们需要在不同的计算机或者虚拟机上运行5个master节点来保证他们大多数情况下都不会同时宕机。一个客户端需要做如下操作来获取锁:

1、获取当前时间(单位是毫秒)。

2、轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。

3、客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。

4、如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。

5、如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

Redisson 实现方式(红锁 RedLock)

github Redisson
https://github.com/redisson/r...

Maven

<!-- JDK 1.8+ compatible -->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.9.0</version>
</dependency>  

<!-- JDK 1.6+ compatible -->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>2.14.0</version>
</dependency>

集群模式配置

集群模式除了适用于Redis集群环境,也适用于任何云计算服务商提供的集群模式,例如AWS ElastiCache集群版、Azure Redis Cache和阿里云(Aliyun)的云数据库Redis版。

程序化配置集群的用法:

@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useClusterServers()
            .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
            //可以用"rediss://"来启用SSL连接
            .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
            .addNodeAddress("redis://127.0.0.1:7002");
    return Redisson.create(config);
}

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

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

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

Redisson 监控锁

大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

往期精彩文章

  • Redis服务器被攻击后该如何安全加固
  • MySQL从删库到恢复,还用跑路吗?
  • 理解JWT鉴权的应用场景及使用建议
  • 浅谈偏向锁、轻量级锁、重量级锁
  • 算法:一致性哈希算法的理解与实践
  • 架构:通过案例读懂 RESTful 架构风格
  • 架构:一文读懂Apache Flink架构及特性分析
  • 架构:大数据推荐系统实时架构和离线架构
  • 微服务:架构下静态数据通用缓存机制
  • 微服务:小型系统如何“微服务”开发
  • 微服务:深入理解为什么要设计幂等性的服务
  • 中间件:应用消息中间件设计可以解决哪些实际问题?

图片描述


http://www.niftyadmin.cn/n/2432894.html

相关文章

复杂事件处理(CEP)语句

本页目录语法示例MATCH_RECOGNIZE用于从输入流中识别符合指定规则的事件&#xff0c;并按照指定的方式输出。 语法 SELECT [ ALL | DISTINCT ]{ * | projectItem [, projectItem ]* }FROM tableExpression[MATCH_RECOGNIZE ([PARTITION BY {partitionItem [, partitionItem]*}]…

45.Linux 目录操作

opendir函数 根据传入的目录名打开一个目录&#xff08;库函数&#xff09; DIR*类似于FILE* DIR *opendir&#xff08;const char *name&#xff09;; 成功返回 指向该目录结构体指针 失败返回NULL 参数支持相对路径、绝对路径两种方式&#xff1a;例…

Java读取 Mysql的 datetime类型

Java读取 Mysql的 datetime类型 1. 在Mysql数据库中使用DATETIME类型来存储时间&#xff0c;使用JDBC中读取这个字段的时候&#xff0c;应该使用 ResultSet.getTimestamp()&#xff0c;这样会得到一个java.sql.Timestamp类型的数据。 2. 在这里既不能使用 ResultSet.getDate()&…

46.Linux 重定向(duplicate)复制

&#xff08;duplicate&#xff09;复制 dup和dup2函数 int dup(int oldfd); oldfd:已有的文件描述符 返回:新文件描述符 #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int …

vue-cli#2.0项目结构分析

项目结构 build 构建工具相关的目录 config 配置目录 dist 通过工具打包生成的最终需要上线的目录 node_modules 存放本地开发所有的依赖包的目录 src 源码目录 static 存放图片等静态资源的目录 .babelrc babelrc是把新的ES语法&#xff0c;编译成浏览器兼容的语法的编译器&am…

MySql 5.7中添加用户,新建数据库,用户授权,删除用户,修改密码

MySql 5.7中添加用户,新建数据库,用户授权,删除用户,修改密码 2017-01-05 14:32 2947人阅读 评论(0) 收藏 举报分类&#xff1a;mysql&#xff08;1&#xff09; 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 1、新建用户 创建test用户&#xf…

Hadoop系列(三):hadoop基本测试

下面是对hadoop的一些基本测试示例 Hadoop自带测试类简单使用 这个测试类名叫做 hadoop-mapreduce-client-jobclient.jar&#xff0c;位置在 hadoop/share/hadoop/mapreduce/ 目录下 不带任何参数可以获取这个jar的帮助信息 $ yarn jar hadoop-mapreduce-client-jobclient-2.8.…

mysql5.7创建用户授权删除用户撤销授权

mysql5.7创建用户授权删除用户撤销授权 作者&#xff1a;博主是个懒蛋 字体&#xff1a;[增加 减小] 类型&#xff1a;转载 时间&#xff1a;2017-02-08 我要评论 这篇文章主要介绍了mysql5.7创建用户授权删除用户撤销授权的方法,非常不错&#xff0c;具有参考借鉴价值&#xf…