實戰:Redis集群環境下的-RedLock(真分布式鎖)

2022-01-01 磊哥玩編程

每天為您推送優質技術文章

在不同進程需要互斥地訪問共享資源時,分布式鎖是一種非常有用的技術手段。 有很多三方庫和文章描述如何用Redis實現一個分布式鎖管理器,但是這些庫實現的方式差別很大,而且很多簡單的實現其實只需採用稍微增加一點複雜的設計就可以獲得更好的可靠性。 這篇文章的目的就是嘗試提出一種官方權威的用Redis實現分布式鎖管理器的算法,我們把這個算法稱為RedLock。

Redlock是redis官方提出的實現分布式鎖管理器的算法。這個算法會比一般的普通方法更加安全可靠。關於這個算法的討論可以看下官方文檔。

https://github.com/antirez/redis-doc/blob/master/topics/distlock.md

安全和可靠性保證

在描述我們的設計之前,我們想先提出三個屬性,這三個屬性在我們看來,是實現高效分布式鎖的基礎。

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/redisson

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();

相關焦點

  • Redlock:Redis分布式鎖最牛逼的實現
    普通實現說道Redis分布式鎖大部分人都會想到:setnx+lua,或者知道set key value px milliseconds nx。後一種方式的核心實現命令如下:- 獲取鎖(unique_value可以是UUID等)SET resource_name unique_value NX PX 30000- 釋放鎖(lua腳本中,一定要比較value,防止誤解鎖)if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call
  • Node.js 中實踐基於 Redis 的分布式鎖實現
    在一些分布式環境下、多線程並發編程中,如果對同一資源進行讀寫操作,避免不了的一個就是資源競爭問題,通過引入分布式鎖這一概念,可以解決數據一致性問題
  • 分布式鎖解決方案-Redis
    ## 為什麼要學習分布式鎖解決方案為了解決分布式架構帶來的數據準確性問題!我們用synchronized或者 ReentrantLock(瑞恩吹特) 能解決問題嗎?真實生產環境我們採用集群的方式去訪問秒殺商品(nginx為我們做了負載均衡)。
  • 手把手教你實現基於Redis的分布式鎖
    多機多進程部署的情況,需要依賴一個第三方組件(存儲鎖對象)來實現一個分布式的同步鎖。2. 分布式鎖的必要條件本文主要介紹第三種場景下基於Redis如何實現分布式鎖。現在我們來看看實現一個分布式鎖的必要條件有哪些?
  • Redlock分布式鎖
    它可以保證以下特性:安全特性:互斥訪問,即永遠只有一個 client 能拿到鎖避免死鎖:最終 client 都可能拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的 client crash 了或者出現了網絡分區容錯性:只要大部分 Redis 節點存活就可以正常提供服務怎麼在單節點上實現分布式鎖SET resourcename myrandom_value
  • Redis如何實現分布式鎖?
    文章已收錄Github精選,歡迎Star:https://github.com/yehongzhi前言如果在一個分布式系統中,我們從資料庫中讀取一個數據,然後修改保存,這種情況很容易遇到並發問題。因為讀取和更新保存不是一個原子操作,在並發時就會導致數據的不正確。
  • 從Redis分布式鎖到Redlock的實現,這些運行漏洞你都有發現嗎?
    在失敗的時候可曾懷疑過你在用的分布式鎖真的靠譜嗎?以下是結合自己的踩坑經驗總結的一些經驗之談。用到分布式鎖說明遇到了多個進程共同訪問同一個資源的問題。引入分布式鎖勢必要引入一個第三方的基礎設施,比如 MySQL,Redis,Zookeeper 等。這些實現分布式鎖的基礎設施出問題了,也會影響業務,所以在使用分布式鎖前可以考慮下是否可以不用加鎖的方式實現?
  • 這才叫細:帶你深入理解Redis分布式鎖
    鎖我們都知道,在程序中的作用就是同步工具,保證共享資源在同一時刻只能被一個線程訪問,Java中的鎖我們都很熟悉了,像synchronized 、Lock都是我們經常使用的,但是Java的鎖只能保證單機的時候有效,分布式集群環境就無能為力了,這個時候我們就需要用到分布式鎖。
  • Java都為我們提供了各種鎖,為什麼還需要分布式鎖?
    目前的項目單體結構的基本上已經沒有了,大多是分布式集群或者是微服務這些。既然是多臺伺服器。就免不了資源的共享問題。既然是資源共享就免不了並發的問題。針對這些問題,redis也給出了一個很好的解決方案,那就是分布式鎖。
  • redis分布式鎖解決多進程/多線程下單個進程/單個線程運行
    ,防止數據多次被插入。如果 key 已經持有其他值, SET 就覆寫舊值,無視類型。對於某個原本帶有生存時間(TTL)的鍵來說, 當 SET 命令成功在這個鍵上執行時, 這個鍵原有的 TTL 將被清除。EX second :設置鍵的過期時間為 second 秒。
  • 分布式集群環境下的Session共享解決方案
    而如果我們把web伺服器搭建成分布式的集群,然後利用LVS或Nginx做負載均衡,那麼來自同一用戶的Http請求將有可能被分發到兩個不同的web站點中去。那麼問題就來了,如何保證不同的web站點能夠共享同一份session數據呢?最簡單的想法就是把session數據保存到內存以外的一個統一的地方,例如Memcached/Redis等資料庫中。
  • 對不起,網上找的Redis分布式鎖都有漏洞!
    在失敗的時候可曾懷疑過你在用的分布式鎖真的靠譜嗎?以下是結合自己的踩坑經驗總結的一些經驗之談。用到分布式鎖說明遇到了多個進程共同訪問同一個資源的問題。引入分布式鎖勢必要引入一個第三方的基礎設施,比如 MySQL,Redis,Zookeeper 等。這些實現分布式鎖的基礎設施出問題了,也會影響業務,所以在使用分布式鎖前可以考慮下是否可以不用加鎖的方式實現?
  • Redisson 分布式鎖實戰與watch dog機制解讀
    :互斥:在分布式高並發的條件下,需要保證,同一時刻只能有一個線程獲得鎖,這是最最基本的一點。防止死鎖:在分布式高並發的條件下,比如有個線程獲得鎖的同時,還沒有來得及去釋放鎖,就因為系統故障或者其它原因使它無法執行釋放鎖的命令,導致其它線程都無法獲得鎖,造成死鎖。可重入:我們知道ReentrantLock是可重入鎖,那它的特點就是同一個線程可以重複拿到同一個資源的鎖。
  • 基於Redisson分布式鎖解決秒殺系統的「超賣」問題
    Redis基礎上的一款綜合中間件,除了擁有Redis本身提供的強大功能外,還提供了諸如分布式鎖、分布式服務、延遲隊列、遠程調用等強大的功能。△ Redisson官網首頁截圖本文我們將使用中間件Redisson中強大的功能組件「分布式鎖」,來解決高並發下多線程導致的超賣以及重複秒殺等安全問題。
  • Docker 搭建 Redis Cluster 集群環境
    ❞在 redis-cluster 目錄下執行以下命令:for port in `seq 6371 6376`; do \  mkdir -p ${port}/conf \  && PORT
  • 如何優雅地用Redis實現分布式鎖?
    什麼是分布式鎖在學習Java多線程編程的時候,鎖是一個很重要也很基礎的概念,鎖可以看成是多線程情況下訪問共享資源的一種線程同步機制。這是對於單進程應用而言的,即所有線程都在同一個JVM進程裡的時候,使用Java語言提供的鎖機制可以起到對共享資源進行同步的作用。
  • 造了一個 Redis 分布鎖的輪子,沒想到還學到這麼多東西!!!
    http://doc.redisfans.com/script/eval.html下面我們統一在 Lua 腳本中使用 redis.call(),執行以下命令:eval "return redis.call('set',KEYS[1],ARGV[1])" 1 foo 樓下小黑哥
  • Python 中 Redis 庫分布式鎖簡單分析
    pip install redis==2.10.6這裡以這個庫的2.10.6版本為例,對它Redis分布式鎖源碼進行簡單的分析。,每過sleep秒,就會嘗試去獲取鎖對象blocking_timeout用於指定阻塞超時時間,當多個實例爭奪鎖時,這個時間就是實例等待鎖的最長時間方法中最關鍵的一句代碼為lock_class = self.
  • 走進 Redis:Redis 的安裝、使用以及集群的搭建
    如果我們想後臺啟動就需要:1、進入redis-3.0.0.tar.gz解壓出來的文件夾,複製裡面的redis.conf文件到安裝目錄下。然後將daemonize改為yes2、執行 ./redis-server redis.conf運行redis。
  • redisson分布式讀寫鎖-讀鎖watchDog原理
    redisson分布式鎖-watch dog原理分布式可重入鎖原理-鎖互斥原理分布式可重入鎖原理-釋放鎖原理Redisson分布式可重入鎖-加鎖超時機制redisson分布式公平鎖-加鎖原理redisson分布式公平鎖-隊列重排redisson分布式公平鎖-釋放鎖原理