分布式鎖,是指在分布式的部署環境下,通過鎖機制來讓多客戶端互斥的對共享資源進行訪問。
排他性:在同一時間只會有乙個客戶端能獲取到鎖,其它客戶端無法同時獲取
避免死鎖:這把鎖在一段有限的時間之後,一定會被釋放(正常釋放或異常釋放);可重入鎖(避免死鎖)
高可用:獲取或釋放鎖的機制必須高可用且效能佳
針對分布式鎖的實現,目前比較常用的有以下幾種方案:
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 ...