公司交給了萌新小猿一個光榮而艱鉅的項目,該項目須要使用分佈式鎖,這可難道了小猿,只是據說過度布式鎖很牛掰,其餘就一律不知了,唉不懂就問唄,遂向老闆請教。java
老闆:咱們天天不都在經歷分佈式鎖嗎,我來給你回憶回憶。
小猿:好勒,瓜子板凳已備好。node
這個問題應該拆分紅如下2個問題回答。git
保證在同一時刻共享資源只能被一個客戶端訪問;
根據鎖用途分爲如下兩種:github
共享資源的操做不具有冪等性。
常見於 數據的修改、刪除操做;
面試
在上面的例子中,redis
人物事件 | 系統含義 |
---|---|
經理A-N | 多個線程 |
碼農小猿-調高空調溫度 | 非冪等共享資源 |
祕書的容許 | 獲取鎖 |
主要應用場景是:共享資源的操做具備冪等性;
如 數據的查詢。
既然都具備冪等性了,爲何還須要分佈式鎖呢,一般是爲了效率或性能,避免重複操做(尤爲是消耗資源的操做)。例如咱們常見的緩存方案。算法
人物事件 | 系統含義 |
---|---|
經理A-N | 多個線程 |
碼農小猿-整理昨天的資料 | 冪等共享資源 |
祕書的容許 | 獲取鎖 |
本身存資料 | 緩存 |
因爲此處的資源是冪等的,一般會將這類資源作緩存,這就是常見的鎖+緩存架構。 常適用於 獲取較爲消耗資源(時間、內存、CPU等)的冪等資源,如:數據庫
固然,若是資源僅在一段時間範圍內具備冪等性,這時候,架構就應該升級了:
鎖+緩存+緩存失效/失效從新獲取/緩存定時更新。緩存
仍是以上面的緩存方案爲例,此處略做變化。安全
人物事件 | 系統含義 |
---|---|
系統A、B | 彼此獨立的系統 |
碼農小猿-調高空調溫度 | 非冪等共享資源 |
李祕書的容許 | 獲取鎖 |
王祕書的容許 | 獲取鎖 |
李祕書、王祕書信息絕對互通 | 單一鎖升級爲分佈式鎖 |
PS:若是多個客戶端都能同時得到鎖,那鎖就沒意義了,共享資源的安全性也就沒法保證了。
老闆:當我在會議室接待客戶A時,其餘客戶只有等待,你須要等到我空閒了才能把其餘人帶到我辦公室。
小猿:明白。
接待客戶(非冪等共享資源);等到老闆空閒(獲取鎖)。
例如:客戶端A獲取鎖,鎖釋放時間爲10S,即將到達10S時,客戶端A未完成任務,須要再申請5S。若鎖沒有可重入性,客戶端A將沒法續約,致使鎖可能被其餘客戶端搶走。
小猿:受教了,老闆3分鐘後你還有一場面試。
老闆:小猿啊,可貴你這麼好學,我很欣慰,咱們的交流時間延10分鐘吧,其餘會議延後。
小猿:好的,我已在釘釘申請將會議延長10分鐘了;
老闆:嗯,我已經接受會議邀請了;
小猿:老闆你真高效。
分佈式、微服務環境下,必須保證服務的高可用,不然輕則影響其餘業務模塊,重則引起服務雪崩。
老闆:我手機24小時開機,有會議時聯繫不上我也能夠聯繫我祕書。
鎖阻塞性 | 示例 |
---|---|
非阻塞式 | 常見的工單系統,員工A、B同時想操做訂單1(搶單)。當員工A得到鎖並如願操做訂單1;員工B獲取鎖失敗,不能一直阻塞,應該告知失敗,讓員工B去作其餘事,不然員工B就光明正大上班划水了。 |
阻塞式 | 打電話給老闆審覈方案,老闆在通話中(獲取鎖失敗),此時須要每隔一段時間就給老闆打電話,直到聯繫上老闆才行。誰讓老闆下了死命令今天必須審覈經過呢,嗚嗚嗚。 |
常見的解決方案是,給鎖加隨機數(或ThreadID)。
老闆:小猿啊,給你講了這麼多,都明白了嗎?
籠子裏的鸚鵡:明白啦,明白啦。
老闆:閉嘴,我問的是小猿,只有小猿本身有資格回答。
祕書破門而入:老闆,大家10分鐘的會議已經到點了,隔壁的李總已經等不及了;
老闆:一不留神就忘記時間了,我得去見李總了。
小猿:老闆,咱們還沒聊完呢,,,
小猿:無論出現哪一種狀況,我獲取鎖都會失敗啊,這可怎麼辦呢?
PS:這就複雜了,須要根據具體的業務場景分析。對於必須同步處理的業務,則必須失敗告警,對於容許延遲處理的業務能夠考慮記錄失敗信息待其餘系統處理。
基於Redis的SETNX指令完成鎖的獲取;
lock:resource_name:資源名字,加鎖對象的惟一標記;
random_value:一般存儲加鎖方的惟一標記,如「UUID+ThreadID」;
NX:key不存在才設置,即鎖未被其餘人加鎖才能加鎖;
PX:鎖超時時間;
固然,此種加鎖方式是不支持「鎖重入性」的。
checkValueThenDelete:檢查解鎖方是不是加鎖方,是則容許解鎖,不然不容許解鎖;
僞代碼是:
public class RedisTool {
// 釋放鎖成功標記
private static final Long RELEASE_LOCK_SUCCESS = 1L;
/** * 釋放分佈式鎖 * * @param jedis Redis客戶端 * @param lockKey 鎖標記 * @param lockValue 加鎖方標記 * @return 是否釋放成功 */
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String lockValue) {
String script = "" +
"if redis.call('get', KEYS[1]) == ARGV[1] then" +
" return redis.call('del', KEYS[1]) " +
"else" +
" return 0 " +
"end";
// Collections.singletonList():用於只有一個元素的場景,減小內存分配
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
if (RELEASE_LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
複製代碼
此算法由Redis做者antirez提出,做爲一種分佈式場景下的鎖實現方案;
【核心】大多數節點獲取鎖成功且鎖依舊有效;
分佈式系統專家Martin Kleppmann討論提出RedLock存在安全性問題;
Martin Kleppmann認爲Redis做者antirez提出的RedLock算法有安全性問題,雙方在網絡上多輪探討交鋒。Martin指出RedLock算法的核心問題點以下:
敬請關注後續《玩轉Redis》系列文章。
祝君好運!
Life is all about choices!
未來的你必定會感激如今拼命的本身!
【CSDN】【GitHub】【OSCHINA】【掘金】【語雀】【微信公衆號】