先描述一下業務場景,
場景1:mq訊息處理各個**的資料,進行使用者初始化,寫入detail表,由於mq的重試機制以及高併發環境下,使用者記錄可能存在寫入多次的情況,需要保證初始化時,每個使用者(以下為pin)只能允許乙個事務進行操作,同時每個唯一key也只能允許乙個執行緒進行插入,因此需要使用鎖。
場景2:後台計算worker,根據每個時間段對detail表中新插入的資料進行計算,也是只允許同一時間只有乙個worker在執行,理論上最優方案是通過worker排程平台+dns負載均衡來實現,但為了簡單,還是使用鎖來實現(使用鎖的方案有乙個很嚴重的問題,文末會提到)。
多說無用,直接看第乙個版本
//版本1
public boolean trylock(string key)
}catch (exception e)
cluster.expire(key,1, timeunit.minutes);
return islock;
}
一開始想到的就是通過incr的原子操作進行判斷,當累加數大於1之後,就獲取鎖失敗,在獲取鎖成功之後,設定過期時間,防止死鎖。這種寫法咋一看沒啥問題,不過組內review之後,發現由於incr和expire的操作是分開的,並不是乙個原子操作,因此在incr之後,expire之前,出現錯誤,比如例項出錯導致未執行expire,將導致這個key永遠死鎖,因此迭代出下乙個版本。
//版本2
public boolean trylock(string key)
return false;
}}catch (exception e)
cluster.expire(key,1, timeunit.minutes);
return islock;
}
既然出錯會死鎖,就判斷當再次被鎖時,就刪除這個鎖,想法是很好,但仔細一想就會發現問題,此時的判斷死鎖的數值為2,如果當有4個例項同時請求鎖時,第二個請求會被鎖,第三個也會被鎖,但是第四個就可以獲取到鎖,導致鎖功能失效。
使用incr根本問題在於操作和設定過期時間的非原子型,需要其他的辦法解決,經過參考網上的其他方案之後,有了版本3。
//版本3
public boolean trylock(string key)
long locktimestamp = long.valueof(cluster.get(key));
if (system.currenttimemillis() > locktimestamp)
}return false;
}catch (exception e)
}
通過把過期時間寫入value,由於setnx是原子操作,可以保證只要獲取到鎖,過期時間一定寫入,並且保證過期之後,也能立即失效,是乙個理想的解決方案。
以為這就結束了嗎?太天真了,有加鎖就必然會有解鎖,此時的解鎖**如下:
public void unlock(string key)
long locktimestamp = long.valueof(cluster.get(key));
if (system.currenttimemillis() > locktimestamp)
}
當判斷這個key過期之後,就刪除這個鎖,也是很合理的邏輯,但是也經不起推敲,當業務處理時間超過過期時間,會導致鎖失效,造成資料不一致;
//最終版
private long timeout = timeunit.minutes.tomillis(30);
private threadlocalthreadowner = new threadlocal();
public boolean trylock(string key)
long locktimestamp = long.valueof(cluster.get(key));
if (system.currenttimemillis() > locktimestamp)
}return false;
} catch (exception e)
}public void unlock(string key) else
long locktimestamp = long.valueof(cluster.get(key));
if (system.currenttimemillis() > locktimestamp)
}}
這裡使用執行緒快取來保證只有獲取鎖的例項才能進行刪除鎖操作,同時把過期時間調長,只要保證能超過業務處理時間即可。
最後就是前文提到的使用鎖方案的問題,在多例項的環境下,由於後台計算worker是週期性執行,如果某乙個例項的時間比其他例項時間早,那麼所有的worker都將由一台機器執行,造成效能瓶頸,因此後續將使用排程平台來進行worker的分配。
分布式 分布式鎖
本質是利用redis的setnx 方法的特性來加鎖,setnx 即key不存在則設定key,否則直接返回false,要求在分布式系統中使用同乙個redis服務,以下提供兩種解決方案 1 直接使用redistemplate 這其實並不能完全保證高併發下的安全問題,因為可能在鎖過期之後該執行緒尚未執行完...
分布式專題 分布式鎖
在傳統的單體應用架構中,遇到併發安全性問題時我們可以通過同步鎖synchronized,同步 塊,reentrantlock等方式都可以解決,但隨著業務的發展,單體應用架構不能滿足龐大的使用者請求量,於是分布式系統應用而生,在分布式系統中,由於每個系統都執行在不同的伺服器上,有著不同的jvm,所以j...
分布式快取
分布式快取 原則來說跟應用伺服器分布式應該是一樣,但快取是有狀態的。怎麼樣提高命中?1.最原始的演算法 那就是key hash取模,取到伺服器ip。在大量伺服器伸縮行有問題,加入一台伺服器就有可能讓所有的快取都失效。如 key hash 後是100,取10膜是0,取11膜 1,101 取10膜是1,...