本地鎖
常用的即 synchronize 或 Lock 等 JDK 自帶的鎖,只能鎖住當前進程,僅適用於單體架構服務。 而在分佈式多服務實例場景下必須使用分佈式鎖
2 分佈式鎖
2.1 分佈式鎖的原理
廁所佔坑理論
可同時去一個地方“佔坑”:
- 佔到,就執行邏輯
- 否則等待,直到釋放鎖
可通過自旋方式自旋
“佔坑”可以去Redis、DB、任何所有服務都能訪問的地方。
2.2 分佈式鎖演進
一階段
// 佔分布式鎖,去redis佔坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
if(lock) {
//加鎖成功... 執行業務
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
redisTemplate . delete( key: "lock");//fHßti
return dataF romDb ;
} else {
// 加鎖失敗,重試。synchronized()
// 休眠100ms重試
// 自旋
return getCatalogJsonFromDbwithRedisLock();
}
問題場景
- setnx佔好了坑,但是業務代碼異常或程序在執行過程中宕機,即沒有執行成功刪除鎖邏輯,導致死鎖
解決方案:設置鎖的自動過期,即使沒有刪除,會自動刪除。
階段二
// 1\. 佔分布式鎖,去redis佔坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent( "lock", "110")
if(lock) {
// 加鎖成功...執行業務
// 突然斷電
// 2\. 設置過期時間
redisTemplate.expire("lock", timeout: 30, TimeUnit.SECONDS) ;
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
//刪除鎖
redisTemplate. delete( key; "lock");
return dataFromDb;
} else {
// 加鎖失敗...重試。 synchronized ()
// 休眠100ms重試
// 自旋的方式
return getCatalogJsonF romDbWithRedisLock();
}
問題場景
- setnx設置好,正要去設置過期時間,宕機,又死鎖
解決方案:設置過期時間和佔位必須是原子操作。redis支持使用setNxEx命令
階段三
// 1\. 分佈式鎖佔坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "110", 300, TimeUnit.SECONDS);
if(lock)(
// 加鎖成功,執行業務
// 2\. 設置過期時間,必須和加鎖一起作爲原子性操作
// redisTemplate. expire( "lock", з0, TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
// 刪除鎖
redisTemplate.delete( key: "lock")
return dataFromDb;
else {
// 加鎖失敗,重試
// 休眠100ms重試
// 自旋
return getCatalogJsonFromDbithRedislock()
}
階段四
已經拿到了 lockvalue ,有了 UUID,但是過期了現在!其他人拿到所鎖設置了新值,於是 if 後將別人的鎖刪了!!也就是刪除鎖不是原子操作。
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
String lockValue = redisTemplate.opsForValue().get("lock");
if(uuid.equals(lockValue)) {
// 刪除我自己的鎖
redisTemplate.delete("lock");
}
問題場景
- 如果正好判斷是當前值,正要刪除鎖時,鎖已過期,別人已設置成功新值。那刪除的就是別人的鎖.
- 解決方案
刪除鎖必須保證原子性。使用redis+Lua腳本。
階段五
- 確保加鎖/解鎖都是原子操作
String script =
"if redis.call('get', KEYS[1]) == ARGV[1]
then return redis.call('del', KEYS[1])
else
return 0
end";
保證加鎖【佔位+過期時間】和刪除鎖【判斷+刪除】的原子性。 更難的事情,鎖的自動續期。