1. 程式人生 > >Redisson實現Redis分散式鎖的N種姿勢

Redisson實現Redis分散式鎖的N種姿勢

前幾天發的一篇文章《Redlock:Redis分散式鎖最牛逼的實現》,引起了一些同學的討論,也有一些同學提出了一些疑問,這是好事兒。本文在講解如何使用Redisson實現Redis普通分散式鎖,以及Redlock演算法分散式鎖的幾種方式的同時,也附帶解答這些同學的一些疑問。

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版本。

單機模式

原始碼如下:

 
  1. // 構造redisson實現分散式鎖必要的Config

  2. Config config = new Config();

  3. config.useSingleServer().setAddress(

    "redis://172.29.1.180:5379").setPassword("a123456").setDatabase(0);

  4. // 構造RedissonClient

  5. RedissonClient redissonClient = Redisson.create(config);

  6. // 設定鎖定資源名稱

  7. RLock disLock = redissonClient.getLock("DISLOCK");

  8. boolean isLock;

  9. try {

  10. //嘗試獲取分散式鎖

  11. isLock = disLock.tryLock(500, 15000, TimeUnit

    .MILLISECONDS);

  12. if (isLock) {

  13. //TODO if get lock success, do something;

  14. Thread.sleep(15000);

  15. }

  16. } catch (Exception e) {

  17. } finally {

  18. // 無論如何, 最後都要解鎖

  19. disLock.unlock();

  20. }

通過程式碼可知,經過Redisson的封裝,實現Redis分散式鎖非常方便,我們再看一下Redis中的value是啥,和前文分析一樣,hash結構,key就是資源名稱,field就是UUID+threadId,value就是重入值,在分散式鎖時,這個值為1(Redisson還可以實現重入鎖,那麼這個值就取決於重入次數了):

 
  1. 172.29.1.180:5379> hgetall DISLOCK

  2. 1) "01a6d806-d282-4715-9bec-f51b9aa98110:1"

  3. 2) "1"

哨兵模式

即sentinel模式,實現程式碼和單機模式幾乎一樣,唯一的不同就是Config的構造:

 
  1. Config config = new Config();

  2. config.useSentinelServers().addSentinelAddress(

  3. "redis://172.29.3.245:26378","redis://172.29.3.245:26379", "redis://172.29.3.245:26380")

  4. .setMasterName("mymaster")

  5. .setPassword("a123456").setDatabase(0);

叢集模式

叢集模式構造Config如下:

 
  1. Config config = new Config();

  2. config.useClusterServers().addNodeAddress(

  3. "redis://172.29.3.245:6375","redis://172.29.3.245:6376", "redis://172.29.3.245:6377",

  4. "redis://172.29.3.245:6378","redis://172.29.3.245:6379", "redis://172.29.3.245:6380")

  5. .setPassword("a123456").setScanInterval(5000);

總結

普通分散式實現非常簡單,無論是那種架構,向Redis通過EVAL命令執行LUA指令碼即可。

Redlock分散式鎖

那麼Redlock分散式鎖如何實現呢?以單機模式Redis架構為例,直接看實現程式碼:

 
  1. Config config1 = new Config();

  2. config1.useSingleServer().setAddress("redis://172.29.1.180:5378")

  3. .setPassword("a123456").setDatabase(0);

  4. RedissonClient redissonClient1 = Redisson.create(config1);


  5. Config config2 = new Config();

  6. config2.useSingleServer().setAddress("redis://172.29.1.180:5379")

  7. .setPassword("a123456").setDatabase(0);

  8. RedissonClient redissonClient2 = Redisson.create(config2);


  9. Config config3 = new Config();

  10. config3.useSingleServer().setAddress("redis://172.29.1.180:5380")

  11. .setPassword("a123456").setDatabase(0);

  12. RedissonClient redissonClient3 = Redisson.create(config3);


  13. String resourceName = "REDLOCK";

  14. RLock lock1 = redissonClient1.getLock(resourceName);

  15. RLock lock2 = redissonClient2.getLock(resourceName);

  16. RLock lock3 = redissonClient3.getLock(resourceName);


  17. RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

  18. boolean isLock;

  19. try {

  20. isLock = redLock.tryLock(500, 30000, TimeUnit.MILLISECONDS);

  21. System.out.println("isLock = "+isLock);

  22. if (isLock) {

  23. //TODO if get lock success, do something;

  24. Thread.sleep(30000);

  25. }

  26. } catch (Exception e) {

  27. } finally {

  28. // 無論如何, 最後都要解鎖

  29. System.out.println("");

  30. redLock.unlock();

  31. }

最核心的變化就是 RedissonRedLockredLock=newRedissonRedLock(lock1,lock2,lock3);,因為我這裡是以三個節點為例。

那麼如果是哨兵模式呢?需要搭建3個,或者5個sentinel模式叢集(具體多少個,取決於你)。 那麼如果是叢集模式呢?需要搭建3個,或者5個cluster模式叢集(具體多少個,取決於你)。

實現原理

既然核心變化是使用了RedissonRedLock,那麼我們看一下它的原始碼有什麼不同。這個類是RedissonMultiLock的子類,所以呼叫tryLock方法時,事實上呼叫了RedissonMultiLock的tryLock方法,精簡原始碼如下:

 
  1. public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {

  2. // 實現要點之允許加鎖失敗節點限制(N-(N/2+1))

  3. int failedLocksLimit = failedLocksLimit();

  4. List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());

  5. // 實現要點之遍歷所有節點通過EVAL命令執行lua加鎖

  6. for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {

  7. RLock lock = iterator.next();

  8. boolean lockAcquired;

  9. try {

  10. // 對節點嘗試加鎖

  11. lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);

  12. } catch (RedisConnectionClosedException|RedisResponseTimeoutException e) {

  13. // 如果丟擲這類異常,為了防止加鎖成功,但是響應失敗,需要解鎖

  14. unlockInner(Arrays.asList(lock));

  15. lockAcquired = false;

  16. } catch (Exception e) {

  17. // 丟擲異常表示獲取鎖失敗

  18. lockAcquired = false;

  19. }


  20. if (lockAcquired) {

  21. // 成功獲取鎖集合

  22. acquiredLocks.add(lock);

  23. } else {

  24. // 如果達到了允許加鎖失敗節點限制,那麼break,即此次Redlock加鎖失敗

  25. if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {

  26. break;

  27. }

  28. }

  29. }

  30. return true;

  31. }

很明顯,這段原始碼就是上一篇文章《Redlock:Redis分散式鎖最牛逼的實現》提到的Redlock演算法的完全實現。

以sentinel模式架構為例,如下圖所示,有sentinel-1,sentinel-2,sentinel-3總計3個sentinel模式叢集,如果要獲取分散式鎖,那麼需要向這3個sentinel叢集通過EVAL命令執行LUA指令碼,需要3/2+1=2,即至少2個sentinel叢集響應成功,才算成功的以Redlock演算法獲取到分散式鎖:

b16702cdf90e9a0db3602c26c45871608528a31f

問題合集

e32b46af71c126d44b767a40c769d5f1d13af61a 根據上面實現原理的分析,這位同學應該是對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。如何選擇,取決於你的業務!


原文釋出時間為: 2018-12-03
本文作者:阿飛的部落格
本文來自雲棲社群合作伙伴“ Java技術驛站  ”,瞭解相關資訊可以關注“ Java技術驛站 ”。