Java分布式鎖的實現

2021-09-18 04:51:18 字數 3593 閱讀 8987

分布式鎖,是指在分布式的部署環境下,通過鎖機制來讓多客戶端互斥的對共享資源進行訪問。

排他性:在同一時間只會有乙個客戶端能獲取到鎖,其它客戶端無法同時獲取

避免死鎖:這把鎖在一段有限的時間之後,一定會被釋放(正常釋放或異常釋放);可重入鎖(避免死鎖)

高可用:獲取或釋放鎖的機制必須高可用且效能佳

針對分布式鎖的實現,目前比較常用的有以下幾種方案:

1.基於資料庫實現

2.基於快取(redis,memcached)實現

3.基於zookeeper實現

基於資料庫的實現方式的核心思想是:在資料庫中建立乙個表,表中包含方法名等字段,並在方法名字段上建立唯一索引,想要執行某個方法,就使用這個方法名向表中插入資料,成功插入則獲取鎖,執行完成後刪除對應的行資料釋放鎖。

使用基於資料庫的這種實現方式很簡單,但是對於分布式鎖應該具備的條件來說,它有一些問題需要解決及優化:

1、因為是基於資料庫實現的,資料庫的可用性和效能將直接影響分布式鎖的可用性及效能,所以,資料庫需要雙機部署、資料同步、主備切換;

2、不具備可重入的特性,因為同乙個執行緒在釋放鎖之前,行資料一直存在,無法再次成功插入資料,所以,需要在表中新增一列,用於記錄當前獲取到鎖的機器和執行緒資訊,在再次獲取鎖的時候,先查詢表中機器和執行緒資訊是否和當前機器和執行緒相同,若相同則直接獲取鎖;

3、沒有鎖失效機制,因為有可能出現成功插入資料後,伺服器宕機了,對應的資料沒有被刪除,當服務恢復後一直獲取不到鎖,所以,需要在表中新增一列,用於記錄失效時間,並且需要有定時任務清除這些失效的資料;

4、不具備阻塞鎖特性,獲取不到鎖直接返回失敗,所以需要優化獲取邏輯,迴圈多次去獲取。

5、在實施的過程中會遇到各種不同的問題,為了解決這些問題,實現方式將會越來越複雜;依賴資料庫需要一定的資源開銷,效能問題需要考慮。

基於資料庫來做分布式鎖的話,通常有兩種做法:基於資料庫的樂觀鎖、基於資料庫的悲觀鎖。

樂觀鎖機制其實就是在資料庫表中引入乙個版本號(version)欄位來實現的。

當我們要從資料庫中讀取資料的時候,同時把這個version欄位也讀出來,如果要對讀出來的資料進行更新後寫回資料庫,則需要將version加1,同時將新的資料與新的version更新到資料表中,且必須在更新的時候同時檢查目前資料庫裡version值是不是之前的那個version,如果是,則正常更新。如果不是,則更新失敗,說明在這個過程中有其它的程序去更新過資料了。

使用「樂觀鎖」機制,必須得滿足:

(1)鎖服務要有遞增的版本號version (2)每次更新資料的時候都必須先判斷版本號對不對,然後再寫入新的版本號

悲觀鎖也叫作排它鎖,在mysql中是基於for update來實現加鎖的,例如:

上面的示例中,user表中,id是主鍵,通過 for update 操作,資料庫在查詢的時候就會給這條記錄加上排它鎖。

(需要注意的是,在innodb中只有欄位加了索引的,才會是行級鎖,否則會是表級鎖,所以這個id欄位要加索引)

當這條記錄加上排它鎖之後,其它執行緒是無法操作這條記錄的。

那麼,這樣的話,我們就可以認為獲得了排它鎖的這個執行緒是擁有了分布式鎖,然後就可以執行我們想要做的業務邏輯,當邏輯完成之後,再呼叫上述釋放鎖的語句即可。

1、選用redis實現分布式鎖原因:

(1)redis有很高的效能; 

(2)redis命令對此支援較好,實現起來比較方便

2、使用命令介紹:

(1)setnx:加鎖

setnx key val:當且僅當key不存在時,set乙個key為val的字串,返回1;若key存在,則什麼都不做,返回0。

(2)expire:設定超時時間

expire key timeout:為key設定乙個超時時間,單位為second,超過這個時間鎖會自動釋放,避免死鎖。

(3)delete:釋放鎖

delete key:刪除key

在使用redis實現分布式鎖的時候,主要就會使用到這三個命令。

3、實現思想:

(1)獲取鎖的時候,使用setnx加鎖,並使用expire命令為鎖新增乙個超時時間,超過該時間則自動釋放鎖,鎖的value值為乙個隨機生成的uuid,通過此在釋放鎖的時候進行判斷。

(2)獲取鎖的時候還設定乙個獲取的超時時間,若超過這個時間則放棄獲取鎖。

(3)釋放鎖的時候,通過uuid判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。

原理:當某客戶端要進行邏輯的加鎖時,就在zookeeper上的某個指定節點的目錄下,去生成乙個唯一的臨時有序節點, 然後判斷自己是否是這些有序節點中序號最小的乙個,如果是,則算是獲取了鎖。如果不是,則說明沒有獲取到鎖,那麼就需要在序列中找到比自己小的那個節點,並對其呼叫exist()方法,對其註冊事件監聽,當監聽到這個節點被刪除了,那就再去判斷一次自己當初建立的節點是否變成了序列中最小的。如果是,則獲取鎖,如果不是,則重複上述步驟。

zookeeper是乙個為分布式應用提供一致性服務的開源元件,它內部是乙個分層的檔案系統目錄樹結構,規定同乙個目錄下只能有乙個唯一檔名。基於zookeeper實現分布式鎖的步驟如下:

(1)建立乙個目錄mylock; 

(2)執行緒a想獲取鎖就在mylock目錄下建立臨時順序節點;

(3)獲取mylock目錄下所有的子節點,然後獲取比自己小的兄弟節點,如果不存在,則說明當前執行緒順序號最小,獲得鎖; 

(4)執行緒b獲取所有節點,判斷自己不是最小節點,設定監聽比自己次小的節點; 

(5)執行緒a處理完,刪除自己的節點,執行緒b監聽到變更事件,判斷自己是不是最小的節點,如果是則獲得鎖。

這裡推薦乙個apache的開源庫curator,它是乙個zookeeper客戶端,curator提供的interprocessmutex是分布式鎖的實現,acquire方法用於獲取鎖,release方法用於釋放鎖。

優點:具備高可用、可重入、阻塞鎖特性,可解決失效死鎖問題。

缺點:因為需要頻繁的建立和刪除節點,效能上不如redis方式。

上面幾種方式,哪種方式都無法做到完美。就像cap一樣,在複雜性、可靠性、效能等方面無法同時滿足,所以,根據不同的應用場景選擇最適合自己的才是王道。

從理解的難易程度角度(從低到高)

資料庫 > 快取 > zookeeper

從實現的複雜性角度(從低到高)

zookeeper >= 快取 > 資料庫

從效能角度(從高到低)

快取 > zookeeper >= 資料庫

從可靠性角度(從高到低)

zookeeper > 快取 > 資料庫

JAVA分布式鎖的實現

基於資料庫實現分布式鎖 要實現分布式鎖,最簡單的方式可能就是直接建立一張鎖表,然後通過操作該表中的資料來實現了。當我們要鎖住某個方法或資源時,我們就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄.唯一索引,想要執行某個方法,就使用這個 方法名向表中插入資料,成功插入則獲取鎖,執行完成後刪除對應...

分布式鎖 使用Redis實現分布式鎖

關於分布式鎖的實現,我的前一篇文章講解了如何使用zookeeper實現分布式鎖。關於分布式鎖的背景此處不再做贅述,我們直接討論下如何使用redis實現分布式鎖。關於redis,筆主不打算做長篇大論的介紹,只介紹下redis優秀的特性。支援豐富的資料型別,如string list map set zs...

分布式鎖實現

1,資料庫實現原理 資料庫的行級x鎖。優點 不需要引入第三方應用。缺點 死鎖 對資料庫效能影響,可能較長時間占用資料庫連線資源 如果業務是分庫分表的,可能支援不了 示例 2,快取實現原理 通過setnx是否成功。當且僅當 key 不存在,將 key 的值設為 value 並返回1 若給定的 key ...