用redis构建分布式锁
單實(shí)例的實(shí)現(xiàn)#
從2.6.12版本開始,redis為SET命令增加了一系列選項(xiàng):
EX seconds – 設(shè)置鍵key的過期時(shí)間,單位時(shí)秒
PX milliseconds – 設(shè)置鍵key的過期時(shí)間,單位時(shí)毫秒
NX – 只有鍵key不存在的時(shí)候才會(huì)設(shè)置key的值
XX – 只有鍵key存在的時(shí)候才會(huì)設(shè)置key的值
如果有2個(gè)進(jìn)程(可能位于不同機(jī)器)需要競(jìng)爭(zhēng)某個(gè)資源,可以為這個(gè)資源加鎖,鎖放在redis里面,這樣兩個(gè)進(jìn)程都能訪問到,例如下面的命令:
SET resource-name random-value NX EX max-lock-time
僅當(dāng)key不存在時(shí),設(shè)置一個(gè)鍵值對(duì),并且設(shè)置了key的過期時(shí)間。
如果其中一個(gè)進(jìn)程set成功,那么另外一個(gè)進(jìn)程會(huì)set失敗,只要判斷set命令的返回值,就可以判斷是否加鎖成功。
這里resouce-name是需要加鎖的資源,而random-value每個(gè)進(jìn)程都可以寫唯一值,而max-lock-time是鎖的最大持有時(shí)間。
如何釋放鎖:
a客戶端獲得的鎖(鍵key)已經(jīng)由于過期時(shí)間到了被redis服務(wù)器刪除,但是這個(gè)時(shí)候a客戶端還去執(zhí)行DEL命令。而b客戶端已經(jīng)在a設(shè)置的過期時(shí)間之后重新獲取了這個(gè)同樣key的鎖,那么a執(zhí)行DEL就會(huì)釋放了b客戶端加好的鎖。
if redis.call(“get”,KEYS[1]) == ARGV[1]
then
return redis.call(“del”,KEYS[1])
else
return 0
end
由于每個(gè)進(jìn)程寫入的value是自己生成的隨機(jī)數(shù),可以保證一個(gè)進(jìn)程只能刪除自己加的鎖,而避免誤刪其它進(jìn)程加的鎖。
回到頂部
分布式鎖#
在分布式版本的算法里我們假設(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)獲取鎖:
1.獲取當(dāng)前時(shí)間(單位是毫秒)。
2.輪流用相同的key和隨機(jī)值在N個(gè)節(jié)點(diǎn)上請(qǐng)求鎖,在這一步里,客戶端在每個(gè)master上請(qǐng)求鎖時(shí),會(huì)有一個(gè)和總的鎖釋放時(shí)間相比小的多的超時(shí)時(shí)間。比如如果鎖自動(dòng)釋放時(shí)間是10s,那每個(gè)節(jié)點(diǎn)鎖請(qǐng)求的超時(shí)時(shí)間可能是5~50ms的范圍,這個(gè)可以防止一個(gè)客戶端在某個(gè)宕掉的master節(jié)點(diǎn)上阻塞過長(zhǎng)時(shí)間,如果一個(gè)master節(jié)點(diǎn)不可用了,我們應(yīng)該盡快嘗試下一個(gè)master節(jié)點(diǎn)。
3.客戶端計(jì)算第二步中獲取鎖所花的時(shí)間,只有當(dāng)客戶端在大多數(shù)master節(jié)點(diǎn)上成功獲取了鎖(在這里是3個(gè)),而且總共消耗的時(shí)間不超過鎖釋放時(shí)間,這個(gè)鎖就認(rèn)為是獲取成功了。
4.如果鎖獲取成功了,那現(xiàn)在鎖自動(dòng)釋放時(shí)間就是最初的鎖釋放時(shí)間減去之前獲取鎖所消耗的時(shí)間。
5.如果鎖獲取失敗了,不管是因?yàn)楂@取成功的鎖不超過一半(N/2+1)還是因?yàn)榭傁臅r(shí)間超過了鎖釋放時(shí)間,一定要盡快在獲取鎖成功的節(jié)點(diǎn)上釋放鎖,這樣就沒必要等到key超時(shí)后才能重新獲取這個(gè)鎖(但是如果網(wǎng)絡(luò)分區(qū)的情況發(fā)生而且客戶端無(wú)法連接到Redis節(jié)點(diǎn)時(shí),會(huì)損失等待key超時(shí)這段時(shí)間的系統(tǒng)可用性)。
注意:當(dāng)一個(gè)客戶端獲取鎖失敗時(shí),這個(gè)客戶端應(yīng)該在一個(gè)隨機(jī)延時(shí)后進(jìn)行重試,之所以采用隨機(jī)延時(shí)是為了避免不同客戶端同時(shí)重試導(dǎo)致誰(shuí)都無(wú)法拿到鎖的情況出現(xiàn)。同樣的道理客戶端越快嘗試在大多數(shù)Redis節(jié)點(diǎn)獲取鎖,出現(xiàn)多個(gè)客戶端同時(shí)競(jìng)爭(zhēng)鎖和重試的時(shí)間窗口越小,可能性就越低,所以最完美的情況下,客戶端應(yīng)該用多路傳輸?shù)姆绞酵瑫r(shí)向所有Redis節(jié)點(diǎn)發(fā)送SET命令。
參考文檔:
http://ifeve.com/redis-lock/
https://github.com/SPSCommerce/redlock-py
總結(jié)
以上是生活随笔為你收集整理的用redis构建分布式锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 幂等性问题剖析
- 下一篇: docker安装ActiveMQ