聽到Redis 實現併發鎖,大家應該都很熟悉了,不知道有多少同學踩過redis併發鎖的坑。
最近項目中有同學實現了併發鎖,通過代碼review還是發現有些同學理解的並不深入,爲後續的運行埋下來了巨大的隱患,今天空閒之餘再重溫一下併發鎖,希望多剛接觸redis 鎖的同學有啓發。
首先列舉一下幾種常見的寫法啊
1、第一種,也是在review代碼時經常遇到的
Jedis jedis; public boolean tryLock(String key){ String value = jedis.get(key); if(org.apache.commons.lang3.StringUtils.isEmpty(value)){ jedis.set(key,"1"); jedis.expire(key,1000); return true; } return false; }
這種寫法有幾個問題呢?
1 String value = jedis.get(key); 多線程高併發訪問時,拿到的value都爲空都能拿到鎖,併發控制失效
2 jedis.set(key,"1") ;jedis.expire(key,1000); 非原子操作,設置失效時間失敗時,其他線程將永遠獲取不到鎖。造成死鎖。
2、第二種也是經常遇到的,還是先上代碼纔有說服力
Jedis jedis; public boolean tryLock(String key){ long value = jedis.incr(key); // 已經有其他線程獲取到鎖 if(value != 1) { return false; }else { jedis.expire(key,100); return true; }
}
這種寫法又有什麼問題呢?
首先和第一個有相似之處的是,設置redis失效時間不是原子操作,中間任何異常導致設置失效時間出錯時,會造成後續的死鎖
看了這幾個,有沒有踩過同樣的坑呢?
首先我認爲寫redis 併發鎖,有幾個堅持的原則
A、key和redis的失效時間是不是原子操作
B、get--》compare的方法在高併發場景寫不可取
3、三是我們最常用的,能滿足我們常規加鎖需求
Jedis jedis; // 加鎖 public boolean tryLock(String key){ String result = jedis.setex(key,1000,"1"); if(StringUtils.equals("OK",result)){ return true; } return false; } // 釋放鎖 public void releaseLock(String key){ jedis.del(key); }
這裏的實現思路是客戶端進來加鎖、任務執行完畢後釋放鎖,看上去這種方式是完美的。但......我們redis 設置都是主備的,
Client A 在master上得到鎖,此時redis master Down機,redis將切換到Slave機器,Client B在此時去獲取鎖,
此時Client A的鎖信息還有同步到Slave,這種情況Client A、B 都將獲取到併發鎖。
對於這種情況大家還是多看看redis 官方對併發鎖的實現講解。java 版本的開源實現併發鎖Redisson
注:以上代碼均爲僞代碼,有問題大家可以留言討論,也希望文章對看到的人有些許用途