1 本地鎖
常用的即 synchronize 或 Lock 等 JDK 自帶的鎖播掷,只能鎖住當(dāng)前進(jìn)程,僅適用于單體架構(gòu)服務(wù)蹦漠。而在分布式多服務(wù)實(shí)例場景下必須使用分布式鎖
2 分布式鎖
2.1 分布式鎖的原理
廁所占坑理論
可同時(shí)去一個(gè)地方“占坑”:
占到脱衙,就執(zhí)行邏輯
否則等待,直到釋放鎖
可通過自旋方式自旋
“占坑”可以去Redis傀蓉、DB、任何所有服務(wù)都能訪問的地方职抡。
2.2 分布式鎖演進(jìn)
一階段
// 占分布式鎖葬燎,去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");if(lock) {//加鎖成功... 執(zhí)行業(yè)務(wù)Map> dataFromDb = getDataFromDb();redisTemplate . delete( key:"lock");//fH?tireturndataF romDb ;}else{// 加鎖失敗,重試。synchronized()// 休眠100ms重試// 自旋returngetCatalogJsonFromDbwithRedisLock();}復(fù)制代碼
問題場景
setnx占好了坑谱净,但是業(yè)務(wù)代碼異骋ぐ睿或程序在執(zhí)行過程中宕機(jī),即沒有執(zhí)行成功刪除鎖邏輯壕探,導(dǎo)致死鎖
解決方案:設(shè)置鎖的自動(dòng)過期冈钦,即使沒有刪除,會(huì)自動(dòng)刪除李请。
階段二
// 1. 占分布式鎖瞧筛,去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","110")if(lock) {// 加鎖成功...執(zhí)行業(yè)務(wù)// 突然斷電// 2. 設(shè)置過期時(shí)間redisTemplate.expire("lock", timeout:30, TimeUnit.SECONDS) ;Map> dataFromDb = getDataFromDb();//刪除鎖redisTemplate. delete( key;"lock");returndataFromDb;}else{// 加鎖失敗...重試。 synchronized ()// 休眠100ms重試// 自旋的方式returngetCatalogJsonFromDbWithRedisLock();}復(fù)制代碼
問題場景
setnx設(shè)置好导盅,正要去設(shè)置過期時(shí)間较幌,宕機(jī),又死鎖
解決方案:設(shè)置過期時(shí)間和占位必須是原子操作白翻。redis支持使用setNxEx命令
階段三
// 1. 分布式鎖占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","110",300, TimeUnit.SECONDS);if(lock)(// 加鎖成功乍炉,執(zhí)行業(yè)務(wù)// 2. 設(shè)置過期時(shí)間,必須和加鎖一起作為原子性操作// redisTemplate. expire( "lock", з0, TimeUnit.SECONDS);Map> dataFromDb = getDataFromDb();// 刪除鎖redisTemplate.delete( key:"lock")returndataFromDb;else{// 加鎖失敗嘁字,重試// 休眠100ms重試// 自旋returngetCatalogJsonFromDbithRedislock()}復(fù)制代碼
階段四
已經(jīng)拿到了 lockvalue ,有了 UUID杉畜,但是過期了現(xiàn)在纪蜒!其他人拿到所鎖設(shè)置了新值,于是 if 后將別人的鎖刪了4说纯续!也就是刪除鎖不是原子操作。
Map> dataFromDb = getDataFromDb();String lockValue = redisTemplate.opsForValue().get("lock");if(uuid.equals(lockValue)) {// 刪除我自己的鎖redisTemplate.delete("lock");}復(fù)制代碼
問題場景
如果正好判斷是當(dāng)前值灭袁,正要?jiǎng)h除鎖時(shí)猬错,鎖已過期,別人已設(shè)置成功新值茸歧。那刪除的就是別人的鎖.
解決方案
刪除鎖必須保證原子性倦炒。使用redis+Lua腳本。
階段五
確保加鎖/解鎖都是原子操作
String script ="if redis.call('get', KEYS[1]) == ARGV[1]
then return redis.call('del', KEYS[1])
else
return 0
end";復(fù)制代碼
保證加鎖【占位+過期時(shí)間】和刪除鎖【判斷+刪除】的原子性软瞎。 更難的事情逢唤,鎖的自動(dòng)續(xù)期。
總結(jié)
其實(shí)更麻煩的事情涤浇,還有鎖的自動(dòng)續(xù)期鳖藕。所以不管是大廠還是中小型公司,我們都是直接選擇解決了這些問題的 Redisson只锭!不重復(fù)造輪子著恩,但也要知道該框架到底解決了哪些問題,方便我們遇到問題時(shí)也能快速排查定位。
下一篇我們就開始 redisson 講解他是如何做到鎖續(xù)期的~