首先介紹下什麼是分布式鎖,分布式鎖是針對不同系統多程序之間資料同步的一種解決方案。在不同程序需要互斥地訪問共享資源時,分布式鎖是一種非常有用的技術手段。
先提出三個屬性,這三個屬性,是實現高效分布式鎖的基礎。
當前目標是在多個tomcat上實現關單操作。所謂關單,就是把那些超期未支付的訂單給關閉掉,這裡用的是spring schedule實現,關單部分不在這裡詳述,僅就分布式鎖問題進行探索。
這裡是多個tomcat共享乙個redis集群。分布式鎖用redis實現。同一時間只有乙個tomcat可以獲取鎖,進行關單。
原理這個初始版本理論上不是乙個真正的分布式鎖,但放在這裡是可以實現分布式鎖的功能的。所以我也放在這裡做乙個例子吧。為什麼這個不好呢,之後再說。
幾個命令:
首先設定乙個鎖名+鎖過期時間
的鍵值對到redis中,用setnx
來實現鎖功能。setnx,即"set if not exists",就是只有在不存在這個key的時候才進行set,契合鎖的定義。
如果set成功,也就是沒有這個key,那麼就獲取鎖成功。還要再把鎖expire一下,防止set之後當前系統掛了之後一直沒解鎖,發生死鎖。在這段時間裡該程序可以對共享資源進行操作,進行關單,其他程序無法插手。
如果set失敗,說明redis中已經有這個key了,但是並不代表這個鎖還有效,此時仍需判斷,如果鎖過期了,獲取鎖。
下面是此過程的的流程圖:
要使用redis,需要引入redis相關元件,這裡採用jedis,在pom.xml中新增:
>
>
redis.clientsgroupid
>
>
jedisartifactid
>
>
2.6.0version
>
dependency
>
具體實現**如下:
@scheduled
(cron =
"0 */1 * * * ?"
)//每1分鐘執行一次(每個1分鐘的整數倍)
public
void
closeordertask()
else
else
", const.redis_lock.close_order_task_lock);}
}else
", const.redis_lock.close_order_task_lock);}
} log.
info
("關閉訂單定時任務結束");
}/**
* 關閉訂單,解鎖
* @param lockname :redis中儲存的鎖
*/private
void
closeorder
(string lockname)
, threadname:{}"
, const.redis_lock.close_order_task_lock, thread.
currentthread()
.getname()
);int hour = integer.
parseint
(propertiesutil.
getproperty
("close.order.task.time.hour"))
; iorderservice.
closeorder
(hour)
;//解鎖
redisshardedpoolutil.
del(const.redis_lock.close_order_task_lock)
; log.
info
("獲取{}, threadname:{}"
, const.redis_lock.close_order_task_lock, thread.
currentthread()
.getname()
);log.
info
("*************************===");
}
這是無業務**的簡化理解版:
public
static
boolean
getlock
(jedis jedis, string lockkey,
int timeout)
// 如果鎖存在,獲取鎖的過期時間
string currentvaluestr = jedis.
get(lockkey);if
(currentvaluestr != null && system.
currenttimemillis()
> long.
parselong
(currentvaluestr))}
// 其他情況,一律返回加鎖失敗
return
false
;}
問題分析
這裡會出現什麼問題呢?
首先可以很清楚地看出,這種實現方式麻煩,比較難懂,出錯概率不小;
如果有多個程序同時進行getset操作,可能會出現a設定的value值覆蓋b的value值的情況,此時a和b同時進行了getset,儘管最終獲得鎖的只有乙個程序,假設為b,但是如果a的getset是在b之後的話,那麼就會覆蓋掉b的value值。即:鎖是b的,業務是b在處理,但是過期時間是a的;
但是放在這裡還是ok的,因為a、b的過期時間相差幾乎沒有,既然都是基於當前時間+timeout,兩條指令先後處理,相差時間是微秒級的;
可以看到**中是有給鎖設定有效時間的,而且既在redis中設定了ttl,還將過期時間設為value值。但是還有一種情況:如果鎖已過期失效,但程序任務仍然沒有完成,此時有另乙個程序獲取鎖,那麼就有兩個程序在同時執行此任務,違背了分布式鎖的規則;
要設定乙個合理的時間要考慮到執行時間問題、阻塞的問題,同時要兼顧效率。在我這個情境下執行時間是可以**,因此這個問題可以不用考慮;
對時間同步性的要求高。鎖過期時間是由程序自己生成的,那麼多機部署的時候就要求時間是同步的,否則會出現某個鎖時間過長/過短的情況。
setnx和expire是兩步操作,不具有原子性。在併發中原子性是是十分重要的乙個屬性。
對初始版本的分析就完了,總結起來就是僅僅可以實現當前這種情景,功能簡易實現麻煩…是否可以有一種完美的,適合所有情況的分布式鎖呢?
原子性命令:
set key value [ex seconds] [px milliseconds] [nx|xx]
其實前面邏輯判斷那麼多就為了無衝突地set,jedis自帶了乙個多引數set:jedis.set(string key, string value, string n***, string expx, long time)
。還是原子性的。
這幾個引數的意思是:
redisson框架也可以十分簡單的實現分布式鎖。**如下:
@scheduled
(cron =
"0 */1 * * * ?"
)//每1分鐘執行一次(每個1分鐘的整數倍)
public
void
closeordertask()
, threadname:{}"
, const.redis_lock.close_order_task_lock, thread.
currentthread()
.getname()
);int hour = integer.
parseint
(propertiesutil.
getproperty
("close.order.task.time.hour"))
;//執行關單業務
// iorderservice.closeorder(hour);
}else
, threadname:{}"
, const.redis_lock.close_order_task_lock, thread.
currentthread()
.getname()
);}}
catch
(interruptedexception e)
finally
//如果獲得了鎖,將其釋放
lock.
unlock()
;//釋放鎖
log.
info
("redisson分布式鎖釋放");
}}
有幾個需要注意的點:
這裡redis中存入的鎖是hash型別;
注意要在finally中釋放鎖。
目前來說最好的redis分布式方案是redis作者antirez 的redlock,這種方案也只是做到了更好,遠遠沒有到完美的程度。
Redis分布式鎖的正確實現方式
分布式鎖一般有三種實現方式 1.資料庫樂觀鎖 2.基於redis的分布式鎖 3.基於zookeeper的分布式鎖。為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件 1.互斥性。在任意時刻,只有乙個客戶端能持有鎖。2.不會發生死鎖。即使有乙個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能...
Redis分布式鎖的正確實現方式
首先,set 加入了nx引數,可以保證如果已有key存在,則函式不會呼叫成功,也就是只有乙個客戶端能持有鎖,滿足互斥性。其次,由於我們對鎖設定了過期時間,即使鎖的持有者後續發生崩潰而沒有解鎖,鎖也會因為到了過期時間而自動解鎖 即key被刪除 不會發生死鎖。最後,因為我們將value賦值為reques...
c 中正確實現單例
方法一 class mysingleton private static object s lock private static mysingleton s single null public static mysingleton singleton monitor.enter s lock i...