【07期】Redis中是如何實(shí)現(xiàn)分布式鎖的?
閱讀本文大概需要 4.5 分鐘。
來(lái)自:java面試題精選
數(shù)據(jù)庫(kù)樂(lè)觀鎖;
基于Redis的分布式鎖;
基于ZooKeeper的分布式鎖。
要點(diǎn)
在任意時(shí)刻,只有一個(gè)客戶(hù)端能持有鎖。
客戶(hù)端在持有鎖的期間崩潰而沒(méi)有主動(dòng)解鎖,也能保證后續(xù)其他客戶(hù)端能加鎖。
只要大部分的Redis節(jié)點(diǎn)正常運(yùn)行,客戶(hù)端就可以加鎖和解鎖。
實(shí)現(xiàn)
set key value px milliseconds nx?命令實(shí)現(xiàn)加鎖, 通過(guò)Lua腳本實(shí)現(xiàn)解鎖。//獲取鎖(unique_value可以是UUID等)
SET?resource_name?unique_value?NX?PX??30000
//釋放鎖(lua腳本中,一定要比較value,防止誤解鎖)
if?redis.call("get",KEYS[1])?==?ARGV[1]?then
????return?redis.call("del",KEYS[1])
else
????return?0
end
set 命令要用?
set key value px milliseconds nx,替代?setnx + expire?需要分兩次執(zhí)行命令的方式,保證了原子性,value 要具有唯一性,可以使用
UUID.randomUUID().toString()方法生成,用來(lái)標(biāo)識(shí)這把鎖是屬于哪個(gè)請(qǐng)求加的,在解鎖的時(shí)候就可以有依據(jù);釋放鎖時(shí)要驗(yàn)證 value 值,防止誤解鎖;
通過(guò) Lua 腳本來(lái)避免 Check And Set 模型的并發(fā)問(wèn)題,因?yàn)樵卺尫沛i的時(shí)候因?yàn)樯婕暗蕉鄠€(gè)Redis操作 (利用了eval命令執(zhí)行Lua腳本的原子性);
客戶(hù)端A從master獲取到鎖
在master將鎖同步到slave之前,master宕掉了(Redis的主從同步通常是異步的)。
主從切換,slave節(jié)點(diǎn)被晉級(jí)為master節(jié)點(diǎn)客戶(hù)端B取得了同一個(gè)資源被客戶(hù)端A已經(jīng)獲取到的另外一個(gè)鎖。導(dǎo)致存在同一時(shí)刻存不止一個(gè)線程獲取到鎖的情況。
redlock算法出現(xiàn)
獲取當(dāng)前時(shí)間戳,單位是毫秒;
跟上面類(lèi)似,輪流嘗試在每個(gè) master 節(jié)點(diǎn)上創(chuàng)建鎖,過(guò)期時(shí)間較短,一般就幾十毫秒;
嘗試在大多數(shù)節(jié)點(diǎn)上建立一個(gè)鎖,比如 5 個(gè)節(jié)點(diǎn)就要求是 3 個(gè)節(jié)點(diǎn) n / 2 + 1;
客戶(hù)端計(jì)算建立好鎖的時(shí)間,如果建立鎖的時(shí)間小于超時(shí)時(shí)間,就算建立成功了;
要是鎖建立失敗了,那么就依次之前建立過(guò)的鎖刪除;
只要?jiǎng)e人建立了一把分布式鎖,你就得不斷輪詢(xún)?nèi)L試獲取鎖。

https://redis.io/topics/distlock 。
Redisson實(shí)現(xiàn)
//?1.構(gòu)造redisson實(shí)現(xiàn)分布式鎖必要的Config
Config?config?=?new?Config();
config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);
//?2.構(gòu)造RedissonClient
RedissonClient?redissonClient?=?Redisson.create(config);
//?3.獲取鎖對(duì)象實(shí)例(無(wú)法保證是按線程的順序獲取到)
RLock?rLock?=?redissonClient.getLock(lockKey);
try?{
????/**
?????*?4.嘗試獲取鎖
?????*?waitTimeout?嘗試獲取鎖的最大等待時(shí)間,超過(guò)這個(gè)值,則認(rèn)為獲取鎖失敗
?????*?leaseTime???鎖的持有時(shí)間,超過(guò)這個(gè)時(shí)間鎖會(huì)自動(dòng)失效(值應(yīng)設(shè)置為大于業(yè)務(wù)處理的時(shí)間,確保在鎖有效期內(nèi)業(yè)務(wù)能處理完)
?????*/
????boolean?res?=?rLock.tryLock((long)waitTimeout,?(long)leaseTime,?TimeUnit.SECONDS);
????if?(res)?{
????????//成功獲得鎖,在這里處理業(yè)務(wù)
????}
}?catch?(Exception?e)?{
????throw?new?RuntimeException("aquire?lock?fail");
}finally{
????//無(wú)論如何,?最后都要解鎖
????rLock.unlock();
}


參考
https://github.com/javazhiyin/advanced-java/
https://crazyfzw.github.io/2019/04/15/distributed-locks-with-redis/
推薦閱讀:
某程序員動(dòng)了公司的祖?zhèn)鞔a“屎山”,半年后怒交辭職報(bào)告!
有點(diǎn)牛逼,滴滴開(kāi)源的分布式id生成系統(tǒng)
微信掃描二維碼,關(guān)注我的公眾號(hào)
朕已閱?

