鎖,就是爲了防止多個線程併發操做共享變量而致使不可預期的結果鎖採起的一種串行化的方式,那分佈式鎖 ,顧名思義,也就是在控制分佈式條件下,線程可以串行化的操做共享變量,從而達到不一樣機器的進程的線程之間的同步或者互斥。java
分佈式鎖常見的實現方式有redis的實現,zk的實現,tair的實現,本篇咱們主要討論下經過redis的實現。redis
話很少說,咱們先上代碼數據庫
class RedisLock{
public boolean lock(String key, V v, int expireTime){
int retry = 0;
//獲取鎖失敗最多嘗試10次
while (retry < failRetryTimes){
//獲取鎖
Boolean result = redis.setNx(key, v, expireTime);
if (result){
return true;
}
try {
//獲取鎖失敗間隔一段時間重試
TimeUnit.MILLISECONDS.sleep(sleepInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
public boolean unlock(String key){
return redis.delete(key);
}
}
複製代碼
這是分佈式鎖簡單的實現,先嚐試往redis裏面設值,若是成功則返回true,不然睡眠指定的時間重試,直到獲取成功。 若是超太重試的次數獲取鎖仍是失敗的話,就返回false。api
咱們看下上面的實現,會發現如下存在的幾個問題:服務器
Boolean result = redis.setNx(key, v, expireTime);
去設置k,v的時候,若是此時返回的false是因爲超時致使,而實際redis是執行成功了, 那咱們從新再設置就會一直失敗,咱們就會在這裏空等待一個expireTime的時間週期。根據上面的兩點的不足,咱們改進下鎖的實現代碼:網絡
public class RedisLock {
public boolean lock(String key, V v, int expireTime){
int retry = 0;
//獲取鎖失敗最多嘗試10次
while (retry < failRetryTimes){
//1.先獲取鎖,若是是當前線程已經持有,則直接返回
//2.防止後面設置鎖超時,實際上是設置成功,而網絡超時致使客戶端返回失敗,因此獲取鎖以前須要查詢一下
V value = redis.get(key);
//若是當前鎖存在,而且屬於當前線程持有,直接返回
if (null != value && value.equals(v)){
return true;
}
//獲取鎖
Boolean result = redis.setNx(key, v, expireTime);
if (result){
return true;
}
try {
//獲取鎖失敗間隔一段時間重試
TimeUnit.MILLISECONDS.sleep(sleepInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
public boolean unlock(String key, String requestId){
String value = redis.get(key);
//鎖應該是已經超時了,其實這裏能夠加一些監控去看下
if (Strings.isNullOrEmpty(value)){
return true;
}
//判斷當前鎖的持有者是不是當前線程,若是是的話釋放鎖,不是的話返回false
if (value.equals(requestId)){
redis.delete(key);
return true;
}
return false;
}
}
複製代碼
能夠看到,咱們針對上面不足的兩點已經作了些改進,這部分基本上已經知足了咱們業務的需求。 其實,咱們仍是能夠作一些優化的,並不是必須,不一樣的業務能夠有不一樣的實現方式:併發
在咱們分佈式鎖的實際使用中,有遇到過一些考慮不足的點,這裏簡單列一下:分佈式
網上常常還能夠看到這種實現方式,就是獲取到鎖以後要檢查下鎖的過時時間,若是鎖過時了要從新設置下時間,大體代碼以下:優化
public boolean tryLock2(String key, int expireTime){
long expires = System.currentTimeMillis() + expireTime;
//獲取鎖
Boolean result = redis.setNx(key, expires, expireTime);
if (result){
return true;
}
V value = redis.get(key);
if (value != null && (Long)value < System.currentTimeMillis()){
//鎖已通過期
String oldValue = redis.getSet(key, expireTime);
if (oldValue != null && oldValue.equals(value)){
return true;
}
}
return false;
}
複製代碼
這種實現存在的問題,過分依賴當前服務器的時間了,若是在大量的併發請求下,都判斷出了鎖過時,而這個時候再去設置鎖的時候,最終是會只有一個線程,可是可能會致使不一樣服務器根據自身不一樣的時間覆蓋掉最終獲取鎖的那個線程設置的時間。lua
網上有關zk實現的代碼比較多,這裏就不展現代碼了,能夠大體說下思路:
只須要刪除步驟2中建立的節點便可
經過tair來實現分佈式鎖和redis的實現核心差很少,不過tair有個很方便的api,感受是實現分佈式鎖的最佳配置,就是put api調用的時候須要傳入一個version,就和數據庫的樂觀鎖同樣,修改數據以後,版本會自動累加,若是傳入的版本和當前數據版本不一致,就不容許修改,具體能夠看下這篇文章的實現:Tair分佈式鎖這裏就再也不多說了
分佈式鎖的常見實現方式,更多的經過redis和tair比較多一些。固然其使用的過程當中存在的問題還有好多咱們沒有說起到,好比redis集羣模式下,master down以後,鎖若是還沒來得及同步到從,那這個時候也會致使業務出現問題。 這裏也是想說明下,具體使用方式仍是須要根據不一樣的業務的需求進行考量,畢竟咱們使用這個是基於業務,須要保證業務的穩定運行。