Redis 分佈式鎖真的安全嗎?
今天我們來聊一聊 Redis 分佈式鎖。
首先大家可以先思考一個簡單的問題,爲什麼要使用分佈式鎖?普通的 jvm 鎖爲什麼不可以?
這個時候,大家肯定會吧啦吧啦想到一堆,例如 java 應用屬於進程級,不同的 ecs 中部署相同的應用,他們之間相互獨立。
所以,在分佈式系統中,當有多個客戶端需要獲取鎖時,我們需要分佈式鎖。此時,鎖是保存在一個共享存儲系統中的,可以被多個客戶端共享訪問和獲取。
分佈式鎖(SET NX)
知道了分佈式鎖的使用場景,我們來自己簡單的實現下分佈式鎖:
public class IndexController {
public String deductStock() {
String lockKey = "lock:product_101";
//setNx 獲取分佈式鎖
String clientId = UUID.randomUUID().toString();
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)
if (!result) {
return "error_code";
}
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣減成功,剩餘庫存:" + realStock);
} else {
System.out.println("扣減失敗,庫存不足");
}
} finally {
//解鎖
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
}
以上代碼簡單的實現了一個扣減庫存的業務邏輯,我們拆開來說下都做了什麼事情:
1、首先聲明瞭 lockkey,表示我們需要 set 的 keyName
2、其次 UUID.randomUUID().toString(); 生成該次請求的 requestId,爲什麼需要生成這個唯一的 UUID,後面在解鎖的時候會說到
3、獲取分佈式鎖,通過 stringRedisTemplate.opsForValue().setIfAbsent 來實現,該語句的意思是如果存在該 key 則返回 false,若不存在則進行 key 的設置,設置成功後返回 true,將當前線程獲取的 uuid 設置成 value,給定一個鎖的過期時間,防止該線程無限制持久鎖導致死鎖,也爲了防止該服務器突然宕機,導致其他機器的應用無法獲取該鎖,這個是必須要做的設置,至於過期的時間,可以根據內層業務邏輯的執行時間來決定
4、執行內層的業務邏輯,進行扣庫存的操作
5、業務邏輯執行完成後,走到 finally 的解鎖操作,進行解鎖操作時,首先我們來判斷當前鎖的值是否爲該線程持有的,防止當前線程執行較慢,導致鎖過期,從而刪除了其他線程持有的分佈式鎖,對於該操作,我來舉個例子:
-
時刻 1:線程 A 獲取分佈式鎖,開始執行業務邏輯
-
時刻 2:線程 B 等待分佈式鎖釋放
-
時刻 3:線程 A 所在機器 IO 處理緩慢、GC pause 等問題導致處理緩慢
-
時刻 4:線程 A 依舊處於 block 狀態,鎖過期
-
時刻 5:線程 B 獲取分佈式鎖,開始執行業務邏輯,此時線程 A 結束 block,開始釋放鎖
-
時刻 6:線程 B 處理業務邏輯緩慢,線程 A 釋放分佈式鎖,但是此時釋放的是線程 B 的鎖,導致其他線程可以開始獲取鎖
看到這裏,爲什麼每個請求需要 requestId,並且在釋放鎖的情況下判斷是否是當前的 requestId 是有必要的。
以上,就是一個簡單的分佈式鎖的實現過程。但是你覺得上述實現還存在問題嗎?
答案是肯定的。若是在判斷完分佈式鎖的 value 與 requestId 之後,鎖過期了,依然會存在以上問題。
那麼有沒有什麼辦法可以規避以上問題,讓我們不需要去完成這些實現,只需要專注於業務邏輯呢?
我們可以使用 Redisson,並且 Redisson 有中文文檔,方便英文不好的同學查看(開發團隊中有中國的 jackygurui)。
接下來我們再把上述代碼簡單的改造下就可以規避這些問題:
public class IndexController {
public String deductStock() {
String lockKey = "lock:product_101";
//setNx 獲取分佈式鎖
//String clientId = UUID.randomUUID().toString();
//Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)
//獲取鎖對象
RLock redissonLock = redisson.getLock(lockKey);
//加分佈式鎖
redissonLock.lock();
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣減成功,剩餘庫存:" + realStock);
} else {
System.out.println("扣減失敗,庫存不足");
}
} finally {
//解鎖
//if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
// stringRedisTemplate.delete(lockKey);
//}
//redisson分佈式鎖解鎖
redissonLock.unlock();
}
}
可以看到,使用 redisson 分佈式鎖會簡單很多,我們通過redissonLock.lock()
和redissonLock.unlock()
解決了這個問題,看到這裏,是不是有同學會問,如果服務器宕機了,分佈式鎖會一直存在嗎,也沒有去指定過期時間?
redisson 分佈式鎖中有一個 watchdog 機制,即會給一個 leaseTime,默認爲 30s,到期後鎖自動釋放,如果一直沒有解鎖,watchdog 機制會一直重新設定鎖的過期時間,通過設置 TimeTask,延遲 10s 再次執行鎖續命,將鎖的過期時間重置爲 30s。下面就從 redisson.lock() 的源碼來看下:
lock 的最終加鎖方法:
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
可以看到 lua 腳本中redis.call('pexpire', KEYS[1], ARGV[1]);
對 key 進行設置,並給定了一個internalLockLeaseTime
,給定的internalLockLeaseTime
就是默認的加鎖時間,爲 30s。
接下來我們在看下鎖續命的源碼:
private void scheduleExpirationRenewal(final long threadId) {
if (!expirationRenewalMap.containsKey(this.getEntryName())) {
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
//重新設置鎖過期時間
RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});
future.addListener(new FutureListener<Boolean>() {
public void operationComplete(Future<Boolean> future) throws Exception {
RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
if (!future.isSuccess()) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());
} else {
//獲取方法調用的結果
if ((Boolean)future.getNow()) {
//進行遞歸調用
RedissonLock.this.scheduleExpirationRenewal(threadId);
}
}
}
});
}
//延遲 this.internalLockLeaseTime / 3L 再執行run方法
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {
task.cancel();
}
}
}
從源碼層可以看到,加鎖成功後,會延遲 10s 執行 task 中的 run 方法,然後在 run 方法裏面執行鎖過期時間的重置,如果時間重置成功,則繼續遞歸調用該方法,延遲 10s 後進行鎖續命,若重置鎖時間失敗,則可能表示鎖已釋放,退出該方法。
以上,就是關於一個 redis 分佈式鎖的說明,看到這裏,大家應該對分佈式鎖有一個大致的瞭解了。
但是儘管使用了 redisson 完成分佈式鎖的實現,對於分佈式鎖是否還存在問題,分佈式鎖真的安全嗎?
一般的,線上的環境肯定使用 redis cluster,如果數據量不大,也會使用的 redis sentinal。那麼就存在主從複製的問題,那麼是否會存在這種情況,在主庫設置了分佈式鎖,但是可能由於網絡或其他原因導致數據還沒有同步到從庫,此時主庫宕機,選擇從庫作爲主庫,新主庫中並沒有該鎖的信息,其他線程又可以進行鎖申請,造成了發生線程安全問題的可能。
爲了解決這個問題,redis 的作者實現了 redlock,基於 redlock 的實現有很大的爭論,並且現在已經棄用了,但是我們還是需要了解下原理,以及之後基於這些問題的解決方案。
分佈式鎖 Redlock
Redlock 是基於單 Redis 節點的分佈式鎖在 failover 的時候會產生解決不了的安全性問題而產生的,基於 N 個完全獨立的 Redis 節點。
下面我來看下 redlock 獲取鎖的過程:
運行 Redlock 算法的客戶端依次執行下面各個步驟,來完成獲取鎖的操作:
-
獲取當前時間(毫秒數)。
-
按順序依次向 N 個 Redis 節點執行獲取鎖的操作。這個獲取操作跟前面基於單 Redis 節點的獲取鎖的過程相同,包含隨機字符串
my_random_value
,也包含過期時間 (比如PX 30000
,即鎖的有效時間)。爲了保證在某個 Redis 節點不可用的時候算法能夠繼續運行,這個獲取鎖的操作還有一個超時時間 (time out),它要遠小於鎖的有效時間(幾十毫秒量級)。客戶端在向某個 Redis 節點獲取鎖失敗以後,應該立即嘗試下一個 Redis 節點。這裏的失敗,應該包含任何類型的失敗,比如該 Redis 節點不可用,或者該 Redis 節點上的鎖已經被其它客戶端持有 -
計算整個獲取鎖的過程總共消耗了多長時間,計算方法是用當前時間減去第 1 步記錄的時間。如果客戶端從大多數 Redis 節點(>= N/2+1)成功獲取到了鎖,並且獲取鎖總共消耗的時間沒有超過鎖的有效時間 (lock validity time),那麼這時客戶端才認爲最終獲取鎖成功;否則,認爲最終獲取鎖失敗。
-
如果最終獲取鎖成功了,那麼這個鎖的有效時間應該重新計算,它等於最初的鎖的有效時間減去第 3 步計算出來的獲取鎖消耗的時間。
-
如果最終獲取鎖失敗了(可能由於獲取到鎖的 Redis 節點個數少於 N/2+1,或者整個獲取鎖的過程消耗的時間超過了鎖的最初有效時間),那麼客戶端應該立即向所有 Redis 節點發起釋放鎖的操作。
好了,瞭解了 redlock 獲取鎖的機制之後,我們再來討論下 redlock 會有哪些問題:
問題一:
假設一共有 5 個 Redis 節點:A, B, C, D, E。設想發生瞭如下的事件序列:
-
客戶端 1 成功鎖住了 A, B, C,獲取鎖成功(但 D 和 E 沒有鎖住)。
-
節點 C 崩潰重啓了,但客戶端 1 在 C 上加的鎖沒有持久化下來,丟失了。
-
節點 C 重啓後,客戶端 2 鎖住了 C, D, E,獲取鎖成功。
這樣,客戶端 1 和客戶端 2 同時獲得了鎖(針對同一資源)。
在默認情況下,Redis 的 AOF 持久化方式是每秒寫一次磁盤(即執行 fsync),因此最壞情況下可能丟失 1 秒的數據。爲了儘可能不丟數據,Redis 允許設置成每次修改數據都進行 fsync,但這會降低性能。當然,即使執行了 fsync 也仍然有可能丟失數據(這取決於系統而不是 Redis 的實現)。所以,上面分析的由於節點重啓引發的鎖失效問題,總是有可能出現的。爲了應對這一問題,Redis 作者 antirez 又提出了延遲重啓 (delayed restarts) 的概念。也就是說,一個節點崩潰後,先不立即重啓它,而是等待一段時間再重啓,這段時間應該大於鎖的有效時間 (lock validity time)。這樣的話,這個節點在重啓前所參與的鎖都會過期,它在重啓後就不會對現有的鎖造成影響。
關於 Redlock 還有一點細節值得拿出來分析一下:在最後釋放鎖的時候,antirez 在算法描述中特別強調,客戶端應該向所有 Redis 節點發起釋放鎖的操作。也就是說,即使當時向某個節點獲取鎖沒有成功,在釋放鎖的時候也不應該漏掉這個節點。這是爲什麼呢?設想這樣一種情況,客戶端發給某個 Redis 節點的獲取鎖的請求成功到達了該 Redis 節點,這個節點也成功執行了SET
操作,但是它返回給客戶端的響應包卻丟失了。這在客戶端看來,獲取鎖的請求由於超時而失敗了,但在 Redis 這邊看來,加鎖已經成功了。因此,釋放鎖的時候,客戶端也應該對當時獲取鎖失敗的那些 Redis 節點同樣發起請求。實際上,這種情況在異步通信模型中是有可能發生的:客戶端向服務器通信是正常的,但反方向卻是有問題的。
所以,如果不進行延遲重啓,或者對於同一個主節點進行多個從節點的備份,並要求從節點的同步必須實時跟住主節點,也就是說需要配置 redis 從庫的同步策略,將延遲設置爲最小(主從同步是異步進行的),通過min-replicas-max-lag
(舊版本的 redis 使用min-slaves-max-lag
) 來設置主從庫間進行數據複製時,從庫給主庫發送 ACK 消息的最大延遲(以秒爲單位),也就是說,這個值需要設置爲 0,否則都有可能出現延遲,但是這個實際上在 redis 中是不存在的,min-replicas-max-lag
設置爲 0,就代表着這個配置不生效。redis 本身是爲了高效而存在的,如果因爲需要保證業務的準確性而使用,大大降低了 redis 的性能,建議使用的別的方式。
問題二:
如果客戶端長期阻塞導致鎖過期,那麼它接下來訪問共享資源就不安全了(沒有了鎖的保護)。在 RedLock 中還是存在該問題的。
雖然在獲取鎖之後 Redlock 會去判斷鎖的有效性,如果鎖過期了,則會再去重新拿鎖。但是如果發生在獲取鎖之後,那麼該有效性都得不到保障了。
在上面的時序圖中,假設鎖服務本身是沒有問題的,它總是能保證任一時刻最多隻有一個客戶端獲得鎖。上圖中出現的 lease 這個詞可以暫且認爲就等同於一個帶有自動過期功能的鎖。客戶端 1 在獲得鎖之後發生了很長時間的 GC pause,在此期間,它獲得的鎖過期了,而客戶端 2 獲得了鎖。當客戶端 1 從 GC pause 中恢復過來的時候,它不知道自己持有的鎖已經過期了,它依然向共享資源(上圖中是一個存儲服務)發起了寫數據請求,而這時鎖實際上被客戶端 2 持有,因此兩個客戶端的寫請求就有可能衝突(鎖的互斥作用失效了)。
初看上去,有人可能會說,既然客戶端 1 從 GC pause 中恢復過來以後不知道自己持有的鎖已經過期了,那麼它可以在訪問共享資源之前先判斷一下鎖是否過期。但仔細想想,這絲毫也沒有幫助。因爲 GC pause 可能發生在任意時刻,也許恰好在判斷完之後。
也有人會說,如果客戶端使用沒有 GC 的語言來實現,是不是就沒有這個問題呢?質疑者 Martin 指出,系統環境太複雜,仍然有很多原因導致進程的 pause,比如虛存造成的缺頁故障 (page fault),再比如 CPU 資源的競爭。即使不考慮進程 pause 的情況,網絡延遲也仍然會造成類似的結果。
總結起來就是說,即使鎖服務本身是沒有問題的,而僅僅是客戶端有長時間的 pause 或網絡延遲,仍然會造成兩個客戶端同時訪問共享資源的衝突情況發生。
那怎麼解決這個問題呢?Martin 給出了一種方法,稱爲 fencing token。fencing token 是一個單調遞增的數字,當客戶端成功獲取鎖的時候它隨同鎖一起返回給客戶端。而客戶端訪問共享資源的時候帶着這個 fencing token,這樣提供共享資源的服務就能根據它進行檢查,拒絕掉延遲到來的訪問請求(避免了衝突)。如下圖:
在上圖中,客戶端 1 先獲取到的鎖,因此有一個較小的 fencing token,等於 33,而客戶端 2 後獲取到的鎖,有一個較大的 fencing token,等於 34。客戶端 1 從 GC pause 中恢復過來之後,依然是向存儲服務發送訪問請求,但是帶了 fencing token = 33。存儲服務發現它之前已經處理過 34 的請求,所以會拒絕掉這次 33 的請求。這樣就避免了衝突。
但是,對於客戶端和資源服務器之間的延遲(即發生在算法第 3 步之後的延遲),antirez 是承認所有的分佈式鎖的實現,包括 Redlock,是沒有什麼好辦法來應對的。包括在我們到生產環境中,無法避免分佈式鎖超時。
在討論中,有人提出客戶端 1 和客戶端 2 都發生了 GC pause,兩個 fencing token 都延遲了,它們幾乎同時到達了文件服務器,而且保持了順序。那麼,我們新加入的判斷邏輯,即判斷 fencing token 的合理性,應該對兩個請求都會放過,而放過之後它們幾乎同時在操作文件,還是衝突了。既然 Martin 宣稱 fencing token 能保證分佈式鎖的正確性,那麼上面這種可能的猜測也許是我們理解錯了。但是 Martin 並沒有在後面做出解釋。
問題三:
Redlock 對系統記時 (timing) 的過分依賴,下面給出一個例子(還是假設有 5 個 Redis 節點 A, B, C, D, E):
-
客戶端 1 從 Redis 節點 A, B, C 成功獲取了鎖(多數節點)。由於網絡問題,與 D 和 E 通信失敗。
-
節點 C 上的時鐘發生了向前跳躍,導致它上面維護的鎖快速過期。
-
客戶端 2 從 Redis 節點 C, D, E 成功獲取了同一個資源的鎖(多數節點)。
-
客戶端 1 和客戶端 2 現在都認爲自己持有了鎖。
上面這種情況之所以有可能發生,本質上是因爲 Redlock 的安全性 (safety property) 對系統的時鐘有比較強的依賴,一旦系統的時鐘變得不準確,算法的安全性也就保證不了了。
但是作者反駁到,通過恰當的運維,完全可以避免時鐘發生大的跳動,而 Redlock 對於時鐘的要求在現實系統中是完全可以滿足的。哪怕是手動修改時鐘這種人爲原因,不要那麼做就是了。否則的話,都會出現問題。
說了這麼多關於 Redlock 的問題,到底有沒有什麼分佈式鎖能保證安全性呢?我們接下來再來看看 ZooKeeper 分佈式鎖。
基於 ZooKeeper 的分佈式鎖更安全嗎?
很多人(也包括 Martin 在內)都認爲,如果你想構建一個更安全的分佈式鎖,那麼應該使用 ZooKeeper,而不是 Redis。那麼,爲了對比的目的,讓我們先暫時脫離開本文的題目,討論一下基於 ZooKeeper 的分佈式鎖能提供絕對的安全嗎?它需要 fencing token 機制的保護嗎?
Flavio Junqueira 是 ZooKeeper 的作者之一,他的這篇 blog 就寫在 Martin 和 antirez 發生爭論的那幾天。他在文中給出了一個基於 ZooKeeper 構建分佈式鎖的描述(當然這不是唯一的方式):
-
客戶端嘗試創建一個 znode 節點,比如
/lock
。那麼第一個客戶端就創建成功了,相當於拿到了鎖;而其它的客戶端會創建失敗(znode 已存在),獲取鎖失敗。 -
持有鎖的客戶端訪問共享資源完成後,將 znode 刪掉,這樣其它客戶端接下來就能來獲取鎖了。
-
znode 應該被創建成 ephemeral 的。這是 znode 的一個特性,它保證如果創建 znode 的那個客戶端崩潰了,那麼相應的 znode 會被自動刪除。這保證了鎖一定會被釋放。
看起來這個鎖相當完美,沒有 Redlock 過期時間的問題,而且能在需要的時候讓鎖自動釋放。但仔細考察的話,並不盡然。
ZooKeeper 是怎麼檢測出某個客戶端已經崩潰了呢?實際上,每個客戶端都與 ZooKeeper 的某臺服務器維護着一個 Session,這個 Session 依賴定期的心跳 (heartbeat) 來維持。如果 ZooKeeper 長時間收不到客戶端的心跳(這個時間稱爲 Sesion 的過期時間),那麼它就認爲 Session 過期了,通過這個 Session 所創建的所有的 ephemeral 類型的 znode 節點都會被自動刪除。
設想如下的執行序列:
-
客戶端 1 創建了 znode 節點
/lock
,獲得了鎖。 -
客戶端 1 進入了長時間的 GC pause。
-
客戶端 1 連接到 ZooKeeper 的 Session 過期了。znode 節點
/lock
被自動刪除。 -
客戶端 2 創建了 znode 節點
/lock
,從而獲得了鎖。 -
客戶端 1 從 GC pause 中恢復過來,它仍然認爲自己持有鎖。
最後,客戶端 1 和客戶端 2 都認爲自己持有了鎖,衝突了。這與之前 Martin 在文章中描述的由於 GC pause 導致的分佈式鎖失效的情況類似。
看起來,用 ZooKeeper 實現的分佈式鎖也不一定就是安全的。該有的問題它還是有。但是,ZooKeeper 作爲一個專門爲分佈式應用提供方案的框架,它提供了一些非常好的特性,是 Redis 之類的方案所沒有的。像前面提到的 ephemeral 類型的 znode 自動刪除的功能就是一個例子。
還有一個很有用的特性是 ZooKeeper 的 watch 機制。這個機制可以這樣來使用,比如當客戶端試圖創建/lock
的時候,發現它已經存在了,這時候創建失敗,但客戶端不一定就此對外宣告獲取鎖失敗。客戶端可以進入一種等待狀態,等待當/lock
節點被刪除的時候,ZooKeeper 通過 watch 機制通知它,這樣它就可以繼續完成創建操作(獲取鎖)。這可以讓分佈式鎖在客戶端用起來就像一個本地的鎖一樣:加鎖失敗就阻塞住,直到獲取到鎖爲止。這樣的特性 Redlock 就無法實現。
小結一下,基於 ZooKeeper 的鎖和基於 Redis 的鎖相比在實現特性上有兩個不同:
-
在正常情況下,客戶端可以持有鎖任意長的時間,這可以確保它做完所有需要的資源訪問操作之後再釋放鎖。這避免了基於 Redis 的鎖對於有效時間 (lock validity time) 到底設置多長的兩難問題。實際上,基於 ZooKeeper 的鎖是依靠 Session(心跳)來維持鎖的持有狀態的,而 Redis 不支持 Session。
-
基於 ZooKeeper 的鎖支持在獲取鎖失敗之後等待鎖重新釋放的事件。這讓客戶端對鎖的使用更加靈活。
總結
綜上所述,我們可以得出兩種結論:
-
如果僅是爲了效率 (efficiency),那麼你可以自己選擇你喜歡的一種分佈式鎖的實現。當然,你需要清楚地知道它在安全性上有哪些不足,以及它會帶來什麼後果,這也是爲什麼我們需要了解實現原理的原因,大多數情況下不會出問題,但是就萬一的情況,處理起來可能需要大量的時間定位問題。
-
如果你是爲了正確性 (correctness),那麼請慎之又慎。就目前來說 ZooKeeper 的分佈鎖相對於 redlock 更加合理。
最後,由於 redlock 的出現其實是爲了保證分佈式鎖的可靠性,但是由於實現的種種問題其可靠性並沒有 ZooKeeper 分佈式鎖來的高,對於可容錯的希望效率的場景下,redis 分佈式鎖又可以完全滿足,這也是導致了 redlock 被棄用的原因。
參考: http://zhangtielei.com/posts/blog-redlock-reasoning.html
作者:望靠德州 poker 發家的 cxy
來源:juejin.cn/post/7137224260862361637
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Wy4oroYWheXaexLRaHsdlw