溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Zookeeper怎么寫(xiě)分布式鎖框架

發(fā)布時(shí)間:2021-09-09 18:30:06 來(lái)源:億速云 閱讀:346 作者:chen 欄目:編程語(yǔ)言

這篇文章主要講解了“Zookeeper怎么寫(xiě)分布式鎖框架”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Zookeeper怎么寫(xiě)分布式鎖框架”吧!

 

一、核心代碼如下:

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) {
  

//執(zhí)行set命令
  

Boolean absent = template.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);//1
  

//其實(shí)沒(méi)必要判NULL,這里是為了程序的嚴(yán)謹(jǐn)而加的邏輯
  

if (absent == null) {
  

return false;
  

}
  

//是否成功獲取鎖
  

return true;
  

}
  

public boolean unlock(String key, String value) {
  

//使用Lua腳本:先判斷是否是自己設(shè)置的鎖,再執(zhí)行刪除
  

Long result = template.execute(redisScript, Arrays.asList(key,value));
  

//返回最終結(jié)果
  

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;
  

}
  

}
  

執(zhí)行上面的setIfAbsent()方法就只會(huì)導(dǎo)致兩種結(jié)果:

1. 當(dāng)前沒(méi)有鎖(key不存在),那么就進(jìn)行加鎖操作,并對(duì)鎖設(shè)置個(gè)有效期,同時(shí)value表示加鎖的客戶端。

2. 已有鎖存在,不做任何操作。
  

在任意時(shí)刻,該代碼都能保證只有一個(gè)客戶端能持有鎖,并且每一個(gè)分布式鎖都加了過(guò)期時(shí)間,保證不會(huì)出現(xiàn)死鎖,容錯(cuò)性暫時(shí)不考慮的話,加鎖和解鎖通過(guò)key保證了對(duì)多個(gè)客戶端而言都是同一把鎖,value的作用則是保證對(duì)同一把鎖的加鎖和解鎖操作都是同一個(gè)客戶端。
  

二、為什么上述方案不夠好
  

為了理解我們想要提高的到底是什么,我們先看下當(dāng)前大多數(shù)基于Redis的分布式鎖三方庫(kù)的現(xiàn)狀。 用Redis來(lái)實(shí)現(xiàn)分布式鎖最簡(jiǎn)單的方式就是在實(shí)例里創(chuàng)建一個(gè)鍵值,創(chuàng)建出來(lái)的鍵值一般都是有一個(gè)超時(shí)時(shí)間的(這個(gè)是Redis自帶的超時(shí)特性),所以每個(gè)鎖最終都會(huì)釋放(參見(jiàn)前文屬性2)。而當(dāng)一個(gè)客戶端想要釋放鎖時(shí),它只需要?jiǎng)h除這個(gè)鍵值即可。 表面來(lái)看,這個(gè)方法似乎很管用,但是這里存在一個(gè)問(wèn)題:在我們的系統(tǒng)架構(gòu)里存在一個(gè)單點(diǎn)故障,如果Redis的master節(jié)點(diǎn)宕機(jī)了怎么辦呢?有人可能會(huì)說(shuō):加一個(gè)slave節(jié)點(diǎn)!在master宕機(jī)時(shí)用slave就行了!但是其實(shí)這個(gè)方案明顯是不可行的,因?yàn)檫@種方案無(wú)法保證第1個(gè)安全互斥屬性,因?yàn)镽edis的復(fù)制是異步的。 總的來(lái)說(shuō),這個(gè)方案里有一個(gè)明顯的競(jìng)爭(zhēng)條件(race condition),舉例來(lái)說(shuō):
  

客戶端A在master節(jié)點(diǎn)拿到了鎖。
  

master節(jié)點(diǎn)在把A創(chuàng)建的key寫(xiě)入slave之前宕機(jī)了。
  

slave變成了master節(jié)點(diǎn)
  

B也得到了和A還持有的相同的鎖(因?yàn)樵瓉?lái)的slave里還沒(méi)有A持有鎖的信息)
  

當(dāng)然,在某些特殊場(chǎng)景下,前面提到的這個(gè)方案則完全沒(méi)有問(wèn)題,比如在宕機(jī)期間,多個(gè)客戶端允許同時(shí)都持有鎖,如果你可以容忍這個(gè)問(wèn)題的話,那用這個(gè)基于復(fù)制的方案就完全沒(méi)有問(wèn)題,否則的話我還是建議你對(duì)上述方案進(jìn)行改進(jìn)。比如,考慮使用Redlock算法。
  

三、Redlock算法
  

在分布式版本的算法里我們假設(shè)我們有N個(gè)Redis master節(jié)點(diǎn),這些節(jié)點(diǎn)都是完全獨(dú)立的,我們不用任何復(fù)制或者其他隱含的分布式協(xié)調(diào)算法。我們已經(jīng)描述了如何在單節(jié)點(diǎn)環(huán)境下安全地獲取和釋放鎖。因此我們理所當(dāng)然地應(yīng)當(dāng)用這個(gè)方法在每個(gè)單節(jié)點(diǎn)里來(lái)獲取和釋放鎖。在我們的例子里面我們把N設(shè)成5,這個(gè)數(shù)字是一個(gè)相對(duì)比較合理的數(shù)值,因此我們需要在不同的計(jì)算機(jī)或者虛擬機(jī)上運(yùn)行5個(gè)master節(jié)點(diǎn)來(lái)保證他們大多數(shù)情況下都不會(huì)同時(shí)宕機(jī)。一個(gè)客戶端需要做如下操作來(lái)獲取鎖:
  

獲取當(dāng)前時(shí)間(單位是毫秒)。
  

輪流用相同的key和隨機(jī)值在N個(gè)節(jié)點(diǎn)上請(qǐng)求鎖,在這一步里,客戶端在每個(gè)master上請(qǐng)求鎖時(shí),會(huì)有一個(gè)和總的鎖釋放時(shí)間相比小的多的超時(shí)時(shí)間。比如如果鎖自動(dòng)釋放時(shí)間是10秒鐘,那每個(gè)節(jié)點(diǎn)鎖請(qǐng)求的超時(shí)時(shí)間可能是5-50毫秒的范圍,這個(gè)可以防止一個(gè)客戶端在某個(gè)宕掉的master節(jié)點(diǎn)上阻塞過(guò)長(zhǎng)時(shí)間,如果一個(gè)master節(jié)點(diǎn)不可用了,我們應(yīng)該盡快嘗試下一個(gè)master節(jié)點(diǎn)。

客戶端計(jì)算第二步中獲取鎖所花的時(shí)間,只有當(dāng)客戶端在大多數(shù)master節(jié)點(diǎn)上成功獲取了鎖(在這里是3個(gè)),而且總共消耗的時(shí)間不超過(guò)鎖釋放時(shí)間,這個(gè)鎖就認(rèn)為是獲取成功了。
  

如果鎖獲取成功了,那現(xiàn)在鎖自動(dòng)釋放時(shí)間就是最初的鎖釋放時(shí)間減去之前獲取鎖所消耗的時(shí)間。
  

如果鎖獲取失敗了,不管是因?yàn)楂@取成功的鎖不超過(guò)一半(N/2+1)還是因?yàn)榭傁臅r(shí)間超過(guò)了鎖釋放時(shí)間,客戶端都會(huì)到每個(gè)master節(jié)點(diǎn)上釋放鎖,即便是那些他認(rèn)為沒(méi)有獲取成功的鎖。

感謝各位的閱讀,以上就是“Zookeeper怎么寫(xiě)分布式鎖框架”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Zookeeper怎么寫(xiě)分布式鎖框架這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI