您好,登錄后才能下訂單哦!
這篇文章主要講解了“Zookeeper怎么寫分布式鎖框架”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Zookeeper怎么寫分布式鎖框架”吧!
一、核心代碼如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate template;
@Autowired
private DefaultRedisScript<Long> redisScript;
private static final Long RELEASE_SUCCESS = 1L;
private long timeout = 3000;
public boolean lock(String key, String value) {
//執行set命令
Boolean absent = template.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);//1
//其實沒必要判NULL,這里是為了程序的嚴謹而加的邏輯
if (absent == null) {
return false;
}
//是否成功獲取鎖
return true;
}
public boolean unlock(String key, String value) {
//使用Lua腳本:先判斷是否是自己設置的鎖,再執行刪除
Long result = template.execute(redisScript, Arrays.asList(key,value));
//返回最終結果
return RELEASE_SUCCESS.equals(result);
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
@Bean
public DefaultRedisScript<Long> defaultRedisScript() {
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(Long.class);
defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
return defaultRedisScript;
}
}
執行上面的setIfAbsent()方法就只會導致兩種結果:
1. 當前沒有鎖(key不存在),那么就進行加鎖操作,并對鎖設置個有效期,同時value表示加鎖的客戶端。
2. 已有鎖存在,不做任何操作。
在任意時刻,該代碼都能保證只有一個客戶端能持有鎖,并且每一個分布式鎖都加了過期時間,保證不會出現死鎖,容錯性暫時不考慮的話,加鎖和解鎖通過key保證了對多個客戶端而言都是同一把鎖,value的作用則是保證對同一把鎖的加鎖和解鎖操作都是同一個客戶端。
二、為什么上述方案不夠好
為了理解我們想要提高的到底是什么,我們先看下當前大多數基于Redis的分布式鎖三方庫的現狀。 用Redis來實現分布式鎖最簡單的方式就是在實例里創建一個鍵值,創建出來的鍵值一般都是有一個超時時間的(這個是Redis自帶的超時特性),所以每個鎖最終都會釋放(參見前文屬性2)。而當一個客戶端想要釋放鎖時,它只需要刪除這個鍵值即可。 表面來看,這個方法似乎很管用,但是這里存在一個問題:在我們的系統架構里存在一個單點故障,如果Redis的master節點宕機了怎么辦呢?有人可能會說:加一個slave節點!在master宕機時用slave就行了!但是其實這個方案明顯是不可行的,因為這種方案無法保證第1個安全互斥屬性,因為Redis的復制是異步的。 總的來說,這個方案里有一個明顯的競爭條件(race condition),舉例來說:
客戶端A在master節點拿到了鎖。
master節點在把A創建的key寫入slave之前宕機了。
slave變成了master節點
B也得到了和A還持有的相同的鎖(因為原來的slave里還沒有A持有鎖的信息)
當然,在某些特殊場景下,前面提到的這個方案則完全沒有問題,比如在宕機期間,多個客戶端允許同時都持有鎖,如果你可以容忍這個問題的話,那用這個基于復制的方案就完全沒有問題,否則的話我還是建議你對上述方案進行改進。比如,考慮使用Redlock算法。
三、Redlock算法
在分布式版本的算法里我們假設我們有N個Redis master節點,這些節點都是完全獨立的,我們不用任何復制或者其他隱含的分布式協調算法。我們已經描述了如何在單節點環境下安全地獲取和釋放鎖。因此我們理所當然地應當用這個方法在每個單節點里來獲取和釋放鎖。在我們的例子里面我們把N設成5,這個數字是一個相對比較合理的數值,因此我們需要在不同的計算機或者虛擬機上運行5個master節點來保證他們大多數情況下都不會同時宕機。一個客戶端需要做如下操作來獲取鎖:
獲取當前時間(單位是毫秒)。
輪流用相同的key和隨機值在N個節點上請求鎖,在這一步里,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的范圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該盡快嘗試下一個master節點。
客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這里是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認為是獲取成功了。
如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。
如果鎖獲取失敗了,不管是因為獲取成功的鎖不超過一半(N/2+1)還是因為總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認為沒有獲取成功的鎖。
感謝各位的閱讀,以上就是“Zookeeper怎么寫分布式鎖框架”的內容了,經過本文的學習后,相信大家對Zookeeper怎么寫分布式鎖框架這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。