Redis集羣環境下的-RedLock(真分佈式鎖) 實踐

在不一樣進程須要互斥地訪問共享資源時,分佈式鎖是一種很是有用的技術手段。 有不少三方庫和文章描述如何用Redis實現一個分佈式鎖管理器,可是這些庫實現的方式差異很大,並且不少簡單的實現其實只需採用稍微增長一點複雜的設計就能夠得到更好的可靠性。 這篇文章的目的就是嘗試提出一種官方權威的用Redis實現分佈式鎖管理器的算法,咱們把這個算法稱爲RedLock。html

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

https://github.com/antirez/re...git

安全和可靠性保證

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

一、一致性:互斥,無論任什麼時候候,只有一個客戶端能持有同一個鎖。
二、分區可容忍性:不會死鎖,最終必定會獲得鎖,就算一個持有鎖的客戶端宕掉或者發生網絡分區。
三、可用性:只要大多數Redis節點正常工做,客戶端應該都能獲取和釋放鎖。redis

爲何基於故障切換的方案不夠好

爲了理解咱們想要提升的究竟是什麼,咱們先看下當前大多數基於Redis的分佈式鎖三方庫的現狀。 用Redis來實現分佈式鎖最簡單的方式就是在實例裏建立一個鍵值,建立出來的鍵值通常都是有一個超時時間的(這個是Redis自帶的超時特性),因此每一個鎖最終都會釋放。算法

而當一個客戶端想要釋放鎖時,它只須要刪除這個鍵值便可。 表面來看,這個方法彷佛很管用,可是這裏存在一個問題:在咱們的系統架構裏存在一個單點故障,若是Redis的master節點宕機了怎麼辦呢?有人可能會說:加一個slave節點!在master宕機時用slave就好了!可是其實這個方案明顯是不可行的,由於這種方案沒法保證第1個安全互斥屬性,由於Redis的複製是異步的。 總的來講,這個方案裏有一個明顯的競爭條件(race condition),舉例來講:數據庫

一、客戶端A在master節點拿到了鎖。
二、master節點在把A建立的key寫入slave以前宕機了。
三、slave變成了master節點
四、B也獲得了和A還持有的相同的鎖(由於原來的slave裏尚未A持有鎖的信息)緩存

固然,在某些特殊場景下,前面提到的這個方案則徹底沒有問題,好比在宕機期間,多個客戶端容許同時都持有鎖,若是你能夠容忍這個問題的話,那用這個基於複製的方案就徹底沒有問題,不然的話咱們仍是建議你採用這篇文章裏接下來要描述的方案。安全

Redlock 簡介

在不一樣進程須要互斥地訪問共享資源時,分佈式鎖是一種很是有用的技術手段。實現高效的分佈式鎖有三個屬性須要考慮:服務器

一、安全屬性:互斥,無論何時,只有一個客戶端持有鎖
二、效率屬性A:不會死鎖
三、效率屬性B:容錯,只要大多數redis節點可以正常工做,客戶端端都能獲取和釋放鎖。

Redlock 算法

在分佈式版本的算法裏咱們假設咱們有N個Redis master節點,這些節點都是徹底獨立的,咱們不用任何複製或者其餘隱含的分佈式協調算法。咱們已經描述瞭如何在單節點環境下安全地獲取和釋放鎖。所以咱們理所固然地應當用這個方法在每一個單節點裏來獲取和釋放鎖。在咱們的例子裏面咱們把N設成5,這個數字是一個相對比較合理的數值,所以咱們須要在不一樣的計算機或者虛擬機上運行5個master節點來保證他們大多數狀況下都不會同時宕機。一個客戶端須要作以下操做來獲取鎖:

一、獲取當前時間(單位是毫秒)。

二、輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裏,客戶端在每一個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。好比若是鎖自動釋放時間是10秒鐘,那每一個節點鎖請求的超時時間多是5-50毫秒的範圍,這個能夠防止一個客戶端在某個宕掉的master節點上阻塞過長時間,若是一個master節點不可用了,咱們應該儘快嘗試下一個master節點。

三、客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裏是3個),並且總共消耗的時間不超過鎖釋放時間,這個鎖就認爲是獲取成功了。

四、若是鎖獲取成功了,那如今鎖自動釋放時間就是最初的鎖釋放時間減去以前獲取鎖所消耗的時間。

五、若是鎖獲取失敗了,不論是由於獲取成功的鎖不超過一半(N/2+1)仍是由於總消耗時間超過了鎖釋放時間,客戶端都會到每一個master節點上釋放鎖,即使是那些他認爲沒有獲取成功的鎖。

Redisson 實現方式(紅鎖 RedLock)

github Redisson
https://github.com/redisson/r...

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

往期精彩文章

圖片描述

相關文章
相關標籤/搜索