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

相關文章
相關標籤/搜索