Redis分布式锁之红锁的实现

Redis分布式锁之红锁的实现

目录

一、问题

二、办法

三、原理

四、实战

一、问题

分布式锁,当我们请求一个分布式锁的时候,成功了,但是这时候slave还没有复制我们的锁,masterDown了,我们的应用继续请求锁的时候,会从继任了master的原slave上申请,也会成功。

这就会导致,同一个锁被获取了不止一次。

二、办法

Redis中针对此种情况,引入了红锁的概念。

三、原理

用Redis中的多个master实例,来获取锁,只有大多数实例获取到了锁,才算是获取成功。具体的红锁算法分为以下五步:

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

使用相同的key和随机值在N个节点上请求锁。这里获取锁的尝试时间要远远小于锁的超时时间,防止某个masterDown了,我们还在不断的获取锁,而被阻塞过长的时间。

只有在大多数节点上获取到了锁,而且总的获取时间小于锁的超时时间的情况下,认为锁获取成功了。

如果锁获取成功了,锁的超时时间就是最初的锁超时时间进去获取锁的总耗时时间。

如果锁获取失败了,不管是因为获取成功的节点的数目没有过半,还是因为获取锁的耗时超过了锁的释放时间,都会将已经设置了key的master上的key删除。

四、实战

Redission就实现了红锁算法,使用的步骤如下:

1、引入maven

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

2、引入代码

Config config1 = new Config(); config1.useSingleServer().setAddress("redis://172.0.0.1:5378").setPassword("a123456").setDatabase(0); RedissonClient redissonClient1 = Redisson.create(config1); Config config2 = new Config(); config2.useSingleServer().setAddress("redis://172.0.0.1:5379").setPassword("a123456").setDatabase(0); RedissonClient redissonClient2 = Redisson.create(config2); Config config3 = new Config(); config3.useSingleServer().setAddress("redis://172.0.0.1:5380").setPassword("a123456").setDatabase(0); RedissonClient redissonClient3 = Redisson.create(config3); /**  * 获取多个 RLock 对象  */ RLock lock1 = redissonClient1.getLock(lockKey); RLock lock2 = redissonClient2.getLock(lockKey); RLock lock3 = redissonClient3.getLock(lockKey); /**  * 根据多个 RLock 对象构建 RedissonRedLock (最核心的差别就在这里)  */ RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); try {     /**      * 4.尝试获取锁      * waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败      * leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)      */     boolean res = redLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);     if (res) {         //成功获得锁,在这里处理业务     } } catch (Exception e) {     throw new RuntimeException("aquire lock fail"); }finally{     //无论如何, 最后都要解锁     redLock.unlock(); }

3、核心源码

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {     long newLeaseTime = -1;     if (leaseTime != -1) {         newLeaseTime = unit.toMillis(waitTime)*2;     }     long time = System.currentTimeMillis();     long remainTime = -1;     if (waitTime != -1) {         remainTime = unit.toMillis(waitTime);     }     long lockWaitTime = calcLockWaitTime(remainTime);     /**      * 1. 允许加锁失败节点个数限制(N-(N/2+1))      */     int failedLocksLimit = failedLocksLimit();     /**      * 2. 遍历所有节点通过EVAL命令执行lua加锁      */     List<RLock> acquiredLocks = new ArrayList<>(locks.size());     for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {         RLock lock = iterator.next();         boolean lockAcquired;         /**          *  3.对节点尝试加锁          */         try {             if (waitTime == -1 && leaseTime == -1) {                 lockAcquired = lock.tryLock();             } else {                 long awaitTime = Math.min(lockWaitTime, remainTime);                 lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);             }         } catch (RedisResponseTimeoutException e) {             // 如果抛出这类异常,为了防止加锁成功,但是响应失败,需要解锁所有节点             unlockInner(Arrays.asList(lock));             lockAcquired = false;         } catch (Exception e) {             // 抛出异常表示获取锁失败             lockAcquired = false;         }         if (lockAcquired) {             /**              *4. 如果获取到锁则添加到已获取锁集合中              */             acquiredLocks.add(lock);         } else {             /**              * 5. 计算已经申请锁失败的节点是否已经到达 允许加锁失败节点个数限制 (N-(N/2+1))              * 如果已经到达, 就认定最终申请锁失败,则没有必要继续从后面的节点申请了              * 因为 Redlock 算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功              */             if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {                 break;             }             if (failedLocksLimit == 0) {                 unlockInner(acquiredLocks);                 if (waitTime == -1 && leaseTime == -1) {                     return false;                 }                 failedLocksLimit = failedLocksLimit();                 acquiredLocks.clear();                 // reset iterator                 while (iterator.hasPrevious()) {                     iterator.previous();                 }             } else {                 failedLocksLimit--;             }         }         /**          * 6.计算 目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,返回false          */         if (remainTime != -1) {             remainTime -= System.currentTimeMillis() - time;             time = System.currentTimeMillis();             if (remainTime <= 0) {                 unlockInner(acquiredLocks);                 return false;             }         }     }     if (leaseTime != -1) {         List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());         for (RLock rLock : acquiredLocks) {             RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);             futures.add(future);         }         for (RFuture<Boolean> rFuture : futures) {             rFuture.syncUninterruptibly();         }     }     /**      * 7.如果逻辑正常执行完则认为最终申请锁成功,返回true      */     return true; }

到此这篇关于Redis分布式锁之红锁的实现的文章就介绍到这了,更多相关Redis 红锁内容请搜索易知道(ezd.cc)以前的文章或继续浏览下面的相关文章希望大家以后多多支持易知道(ezd.cc)!

推荐阅读

    redis密码设置|如何设置redis密码

    redis密码设置|如何设置redis密码,,如何设置redis密码初始化Redis密码: 在配置文件中有个参数: requirepass 这个就是配置redis访问密码的

    Redis集群批量操作

    Redis集群批量操作,节点,集群,Redis在3.0版正式引入了集群这个特性,扩展变得非常简单。然而当你开心的升级到3.0后,却发现有些很好用的功能

    redis sentinel模式命令集

    redis sentinel模式命令集,状态,频道, ping 订阅模式:ping 服务器回复:*2$4pong$0ping xxx 服务器回复:*2$4pong$3xxx其他模式ping 服

    C# 基于StackExchange.Redis.dll利用Redis实现分布式Session

    C# 基于StackExchange.Redis.dll利用Redis实现分布式Session,令牌,客户端,最近在研发一款O2O产品,考虑到分布式架构的需要,以前那一套.NET的

    REDIS|11 redis做分页

    REDIS|11 redis做分页,分页,每页,之前的数据都加载到了本地java的Map,分页如下@RequestMapping("/articles")String articles(Model mode

    softwaredistribution是什么文件夹

    softwaredistribution是什么文件夹,升级,存放,更新日志,文件,补丁下载,删除,softwaredistribution在Windows目录下,主要是用来存放WINDOWS UPDAT

    centos下redis无法连接

    centos下redis无法连接,防火墙,也会,主要是两方面的原因(1)redis.conf中bind的ip,默认是127.0.0.1,只能本地连接(体现为Could not connect to

    redis bitmap实现点赞的思路

    redis bitmap实现点赞的思路,照片,用户,bitmap简介:bitmap时一连串的二进制数字(0,1),每位所在的位置为偏移(offset),在bitmap上可以执行and、or

    redis的lua脚本多返回值

    redis的lua脚本多返回值,脚本,结果,如何在redis的lua中返回不同类型的多个返回值? lua脚本的函数支持多返回值,若redis调用lua脚本支持多返

    vcredist_x86vcredist_x64区别

    vcredist_x86vcredist_x64区别,应用程序,系统,vcredist_x86vcredist_x64区别Vcredist.exe 为 Visual C++ 应用程序安装最新的运行时组件;V