1 本地鎖
經常使用的即 synchronize 或 Lock 等 JDK 自帶的鎖,只能鎖住當前進程,僅適用於單體架構服務。 而在分佈式多服務實例場景下必須使用分佈式鎖javascript
2 分佈式鎖
2.1 分佈式鎖的原理java
廁所佔坑理論redis
可同時去一個地方「佔坑」:架構
- 佔到,就執行邏輯
- 不然等待,直到釋放鎖
可經過自旋方式自旋分佈式
「佔坑」能夠去Redis、DB、任何全部服務都能訪問的地方。學習
2.2 分佈式鎖演進ui
一階段code
// 佔分布式鎖,去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(); } 關注公衆號:麒麟改bug,可獲取Redis實戰學習筆記一份。
問題場景blog
- 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"; 關注公衆號:麒麟改bug,可獲取Redis實戰學習筆記一份。
保證加鎖【佔位+過時時間】和刪除鎖【判斷+刪除】的原子性。 更難的事情,鎖的自動續期。