Redisson实现Redis分布式锁的N种姿式(转)

Redis几种架构redis

Redis发展到如今,几种常见的部署架构有:算法

  1. 单机模式;
  2. 主从模式;
  3. 哨兵模式;
  4. 集群模式;

咱们首先基于这些架构讲解Redisson普通分布式锁实现,须要注意的是,只有充分了解普通分布式锁是如何实现的,才能更好的了解Redlock分布式锁的实现,由于Redlock分布式锁的实现彻底基于普通分布式锁安全

普通分布式锁架构

Redis普通分布式锁原理这个你们基本上都了解,本文不打算再过多的介绍,上一篇文章Redlock:Redis分布式锁的实现也讲的很细,而且也说到了几个重要的注意点。若是你对Redis普通的分布式锁还有一些疑问,能够再回顾一下这篇文章。框架

接下来直接show you the code,毕竟 talk is cheap。分布式

  • redisson版本

本次测试选择redisson 2.14.1版本。性能

单机模式测试

源码以下:ui

// 构造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还能够实现重入锁,那么这个值就取决于重入次数了):lua

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 { // 实现要点之容许加锁失败节点限制(N-(N/2+1))
    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算法获取到分布式锁:

问题合集

 

根据上面实现原理的分析,这位同窗应该是对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。如何选择,取决于你的业务!

 

 转自: https://mp.weixin.qq.com/s/EcwPnD8jlZrBUzcADcRl6A

相关文章
相关标签/搜索