十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
這篇文章主要介紹“redis分布式鎖的實(shí)現(xiàn)以及用法”,在日常操作中,相信很多人在redis分布式鎖的實(shí)現(xiàn)以及用法問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”redis分布式鎖的實(shí)現(xiàn)以及用法”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)主要從事網(wǎng)站制作、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)招遠(yuǎn),十載網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
redis加鎖命令:
SETNX resource_name my_random_value PX 30000
這個(gè)命令的作用是在只有這個(gè)key不存在的時(shí)候才會(huì)設(shè)置這個(gè)key的值(NX選項(xiàng)的作用),超時(shí)時(shí)間設(shè)為30000毫秒(PX選項(xiàng)的作用) 這個(gè)key的值設(shè)為“my_random_value”。這個(gè)值必須在所有獲取鎖請(qǐng)求的客戶端里保持唯一。
SETNX 值保持唯一的是為了確保安全的釋放鎖,避免誤刪其他客戶端得到的鎖。舉個(gè)例子,一個(gè)客戶端拿到了鎖,被某個(gè)操作阻塞了很長時(shí)間,過了超時(shí)時(shí)間后自動(dòng)釋放了這個(gè)鎖,然后這個(gè)客戶端之后又嘗試刪除這個(gè)其實(shí)已經(jīng)被其他客戶端拿到的鎖。所以單純的用DEL指令有可能造成一個(gè)客戶端刪除了其他客戶端的鎖,通過校驗(yàn)這個(gè)值保證每個(gè)客戶端都用一個(gè)隨機(jī)字符串’簽名’了,這樣每個(gè)鎖就只能被獲得鎖的客戶端刪除了。
既然釋放鎖時(shí)既需要校驗(yàn)這個(gè)值又需要?jiǎng)h除鎖,那么就需要保證原子性,redis支持原子地執(zhí)行一個(gè)lua腳本,所以我們通過lua腳本實(shí)現(xiàn)原子操作。代碼如下:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
如果在加鎖和釋放鎖之間的邏輯執(zhí)行得太長,以至于超出了鎖的超時(shí)限制,就會(huì)出現(xiàn)問題。因?yàn)檫@時(shí)候第一個(gè)線程持有的鎖過期了,臨界區(qū)的邏輯還沒有執(zhí)行完,這個(gè)時(shí)候第二個(gè)線程就提前重新持有了這把鎖,導(dǎo)致臨界區(qū)代碼不能得到嚴(yán)格的串行執(zhí)行。
不難發(fā)現(xiàn)正常情況下鎖操作完后都會(huì)被手動(dòng)釋放,常見的解決方案是調(diào)大鎖的超時(shí)時(shí)間,之后若再出現(xiàn)超時(shí)帶來的并發(fā)問題,人工介入修正數(shù)據(jù)。這也不是一個(gè)完美的方案,因?yàn)榈珮I(yè)務(wù)邏輯執(zhí)行時(shí)間是不可控的,所以還是可能出現(xiàn)超時(shí),當(dāng)前線程的邏輯沒有執(zhí)行完,其它線程乘虛而入。并且如果鎖超時(shí)時(shí)間設(shè)置過長,當(dāng)持有鎖的客戶端宕機(jī),釋放鎖就得依靠redis的超時(shí)時(shí)間,這將導(dǎo)致業(yè)務(wù)在一個(gè)超時(shí)時(shí)間周期內(nèi)不可用。
基本上,如果在執(zhí)行計(jì)算期間發(fā)現(xiàn)鎖快要超時(shí)了,客戶端可以給redis服務(wù)實(shí)例發(fā)送一個(gè)Lua腳本讓redis服務(wù)端延長鎖的時(shí)間,只要這個(gè)鎖的key還存在而且值還等于客戶端設(shè)置的那個(gè)值。 客戶端應(yīng)當(dāng)只有在失效時(shí)間內(nèi)無法延長鎖時(shí)再去重新獲取鎖(基本上這個(gè)和獲取鎖的算法是差不多的)。
當(dāng)鎖超時(shí)時(shí)間快到期且邏輯未執(zhí)行完,延長鎖超時(shí)時(shí)間的偽代碼:
if redis.call("get",KEYS[1]) == ARGV[1] then redis.call("set",KEYS[1],ex=3000) else getDLock();//重新獲取鎖
生產(chǎn)中redis一般是主從模式,主節(jié)點(diǎn)掛掉時(shí),從節(jié)點(diǎn)會(huì)取而代之,客戶端上卻并沒有明顯感知。原先第一個(gè)客戶端在主節(jié)點(diǎn)中申請(qǐng)成功了一把鎖,但是這把鎖還沒有來得及同步到從節(jié)點(diǎn),主節(jié)點(diǎn)突然掛掉了。然后從節(jié)點(diǎn)變成了主節(jié)點(diǎn),這個(gè)新的節(jié)點(diǎn)內(nèi)部沒有這個(gè)鎖,所以當(dāng)另一個(gè)客戶端過來請(qǐng)求加鎖時(shí),立即就批準(zhǔn)了。這樣就會(huì)導(dǎo)致系統(tǒng)中同樣一把鎖被兩個(gè)客戶端同時(shí)持有,不安全性由此產(chǎn)生。
不過這種不安全也僅僅是在主從發(fā)生 failover 的情況下才會(huì)產(chǎn)生,而且持續(xù)時(shí)間極短,業(yè)務(wù)系統(tǒng)多數(shù)情況下可以容忍。
如果你很在乎高可用性,希望掛了一臺(tái) redis 完全不受影響,可以考慮 redlock。 Redlock 算法是由Antirez 發(fā)明的,它的流程比較復(fù)雜,不過已經(jīng)有了很多開源的 library 做了良好的封裝,用戶可以拿來即用,比如 redlock-py。
import redlock addrs = [{ "host": "localhost", "port": 6379, "db": 0 }, { "host": "localhost", "port": 6479, "db": 0 }, { "host": "localhost", "port": 6579, "db": 0 }] dlm = redlock.Redlock(addrs) success = dlm.lock("user-lck-laoqian", 5000) if success: print 'lock success' dlm.unlock('user-lck-laoqian') else: print 'lock failed'
RedLock算法的核心原理:
使用N個(gè)完全獨(dú)立、沒有主從關(guān)系的Redis master節(jié)點(diǎn)以保證他們大多數(shù)情況下都不會(huì)同時(shí)宕機(jī),N一般為奇數(shù)。一個(gè)客戶端需要做如下操作來獲取鎖:
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í)間是10秒鐘,那每個(gè)節(jié)點(diǎn)鎖請(qǐng)求的超時(shí)時(shí)間可能是5-50毫秒的范圍,這個(gè)可以防止一個(gè)客戶端在某個(gè)宕掉的master節(jié)點(diǎn)上阻塞過長時(shí)間,如果一個(gè)master節(jié)點(diǎn)不可用了,我們應(yīng)該盡快嘗試下一個(gè)master節(jié)點(diǎn)。
3.客戶端計(jì)算第二步中獲取鎖所花的時(shí)間,只有當(dāng)客戶端在大多數(shù)master節(jié)點(diǎn)上成功獲取了鎖((N/2) +1),而且總共消耗的時(shí)間不超過鎖釋放時(shí)間,這個(gè)鎖就認(rèn)為是獲取成功了。
4.如果鎖獲取成功了,那現(xiàn)在鎖自動(dòng)釋放時(shí)間就是最初的鎖釋放時(shí)間減去之前獲取鎖所消耗的時(shí)間。
5.如果鎖獲取失敗了,不管是因?yàn)楂@取成功的鎖不超過一半(N/2+1)還是因?yàn)榭傁臅r(shí)間超過了鎖釋放時(shí)間,客戶端都會(huì)到每個(gè)master節(jié)點(diǎn)上釋放鎖,即便是那些他認(rèn)為沒有獲取成功的鎖。
Redis 提供了非常豐富的指令集,但是用戶依然不滿足,希望可以自定義擴(kuò)充若干指令來完成一些特定領(lǐng)域的問題。Redis 為這樣的用戶場(chǎng)景提供了 lua 腳本支持,用戶可以向服務(wù)器發(fā)送 lua 腳本來執(zhí)行自定義動(dòng)作,獲取腳本的響應(yīng)數(shù)據(jù)。Redis 服務(wù)器會(huì)單線程原子性執(zhí)行 lua 腳本,保證 lua 腳本在處理的過程中不會(huì)被任意其它請(qǐng)求打斷。
redis-lua交互--.png
要實(shí)現(xiàn)可重入鎖,方法很簡(jiǎn)單,當(dāng)加鎖失敗時(shí)判斷鎖的值是不是跟當(dāng)前線程設(shè)置值相同,偽代碼如下:
if setnx == 0 if get(key) == my_random_value //重入 else //不可重入 else //獲取了鎖,等價(jià)于可重入
到此,關(guān)于“redis分布式鎖的實(shí)現(xiàn)以及用法”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!