這裏主要記錄項目中使用基於redis的分佈式鎖所遇到的問題及解決方案;
業務場景
我的業務場景是這樣的,我們服務有庫存模塊,而我的服務又是多節點部署,要高峯期會存在庫存差異,後面分析問題之後,打算採用redis實現分佈式鎖(主要的原因是服務已經集成了redis,不需要做額外的配置)
踩坑1. 數據庫事務超時
不要感覺奇怪,分佈式鎖怎麼會導致數據庫事務超時呢?
我的代碼大概是這樣的:
僞代碼
@Transaction(readOnly=false)
void update(){
do{
redis=JedisUtil.getJedis();
flag = getLock(key,redis);
if(flag){
update();
}
}while(true)
}
當你的key長時間獲取不到鎖,並且數據庫事務都有超時時間的限制,那麼就會出現數據庫事務超時問題;
解決方案
數據庫事務改爲手動提交事務;
踩坑2. redis key過期,而業務沒有執行完
我的key的過期時間設置的是30s,如果30秒業務還沒有執行完畢,鎖就會自動釋放,鎖釋放之後,其它線程又會去佔用鎖,同樣會導致問題的發生;
解決方案
最簡單的解決方案就是使用redisson;
如果非要用redis來解決的話,只能使用定時器去檢測key,如果說key還有2秒就快過期了,那麼再爲key重新設置30秒的過期時間;
踩坑3. redis連接池爆滿
分佈式鎖剛加上之後,生產出現一個問題,就是:redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
解決辦法
開始查代碼,發現是開發人員沒有對連接進行釋放;
修復bug之後,又在線上跑了一段時間,又出現了redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
解決辦法
void update(){
do{
redis=JedisUtil.getJedis();
flag = getLock(key,redis);
if(flag){
update();
}else{
// 釋放當前redis連接
// 由於我們的業務場景屬於比較耗時的業務型,所以在這裏休眠1000毫秒
redis.close();
sleep(1000);
}
}while(true)
}
1.當前請求獲取鎖,如果獲取不到,則釋放當前連接,並休眠一會;
2.合理配置redis連接池大小,主要參考具體業務場景的併發量來設置;
踩坑4. 解鈴還須繫鈴人
回顧一下加鎖的參數:
set(key, vlue,"NX","PX", 30000);
其中:value,我使用它來表示加鎖人,必須是一個唯一的標識
比如:
A線程 key=test value=01
B線程 key=test value=02如果A線程執行業務耗時超過了鎖的持有時間,鎖會自動釋放;鎖自動釋放之後,線程B又加鎖成功,但是,此時A線程執行完業務邏輯之後,去釋放鎖,但A線程的鎖已經自動釋放了,如果沒有value來標識的話,它可能就會去釋放B線程的鎖;
踩坑5. redis集羣實現分佈式鎖
這種情況我沒有遇到,因爲公司的redis集羣做了改進;
先說一下這種問題產生的原因:
如果master節點由於某原因發生了主從切換,那麼就會出現鎖丟失的情況;
- 在master節點上拿到了鎖;
- 但是這個加鎖的key還沒有同步到slave節點;
- master故障,發生故障轉移,slave節點升級爲master節點;
- 故導致鎖丟失;
解決辦法
需要通過使用redlock算法;
或使用redisson,它有對redlock算法做封裝;