大多數互聯網系統都是分佈式部署的,分佈式部署確實能帶來性能和效率上的提高,但爲此,咱們就須要多解決一個分佈式環境下,數據一致性的問題。node
當某個資源在多系統之間,具備共享性的時候,爲了保證你們訪問這個資源數據是一致的,那麼就必需要求在同一時刻只能被一個客戶端處理,不能併發的執行,否者就會出現同一時刻有人寫有人讀,你們訪問到的數據就不一致了。redis
1、咱們爲何須要分佈式鎖?sql
在單機時代,雖然不須要分佈式鎖,但也面臨過相似的問題,只不過在單機的狀況下,若是有多個線程要同時訪問某個共享資源的時候,咱們能夠採用線程間加鎖的機制,即當某個線程獲取到這個資源後,就當即對這個資源進行加鎖,當使用完資源以後,再解鎖,其它線程就能夠接着使用了。例如,在JAVA中,甚至專門提供了一些處理鎖機制的一些API(synchronize/Lock等)。數據庫
可是到了分佈式系統的時代,這種線程之間的鎖機制,就沒做用了,系統可能會有多份而且部署在不一樣的機器上,這些資源已經不是在線程之間共享了,而是屬於進程之間共享的資源。網絡
所以,爲了解決這個問題,咱們就必須引入「分佈式鎖」。架構
分佈式鎖,是指在分佈式的部署環境下,經過鎖機制來讓多客戶端互斥的對共享資源進行訪問。併發
分佈式鎖要知足哪些要求呢?分佈式
排他性:在同一時間只會有一個客戶端能獲取到鎖,其它客戶端沒法同時獲取 避免死鎖:這把鎖在一段有限的時間以後,必定會被釋放(正常釋放或異常釋放) 高可用:獲取或釋放鎖的機制必須高可用且性能佳性能
講完了背景和理論,那咱們接下來再看一下分佈式鎖的具體分類和實際運用。學習
2、分佈式鎖的實現方式有哪些?
目前主流的有三種,從實現的複雜度上來看,從上往下難度依次增長:
基於數據庫實現 基於Redis實現 基於ZooKeeper實現
不管哪一種方式,其實都不完美,依舊要根據我們業務的實際場景來選擇。
基於數據庫來作分佈式鎖的話,一般有兩種作法:
基於數據庫的樂觀鎖 基於數據庫的悲觀鎖
咱們先來看一下如何基於「樂觀鎖」來實現:
樂觀鎖機制其實就是在數據庫表中引入一個版本號(version)字段來實現的。
當咱們要從數據庫中讀取數據的時候,同時把這個version字段也讀出來,若是要對讀出來的數據進行更新後寫回數據庫,則須要將version加1,同時將新的數據與新的version更新到數據表中,且必須在更新的時候同時檢查目前數據庫裏version值是否是以前的那個version,若是是,則正常更新。若是不是,則更新失敗,說明在這個過程當中有其它的進程去更新過數據了。
下面找圖舉例:
(圖片來源網絡)
如圖,假設同一個帳戶,用戶A和用戶B都要去進行取款操做,帳戶的原始餘額是2000,用戶A要去取1500,用戶B要去取1000,若是沒有鎖機制的話,在併發的狀況下,可能會出現餘額同時被扣1500和1000,致使最終餘額的不正確甚至是負數。但若是這裏用到樂觀鎖機制,當兩個用戶去數據庫中讀取餘額的時候,除了讀取到2000餘額之外,還讀取了當前的版本號version=1,等用戶A或用戶B去修改數據庫餘額的時候,不管誰先操做,都會將版本號加1,即version=2,那麼另一個用戶去更新的時候就發現版本號不對,已經變成2了,不是當初讀出來時候的1,那麼本次更新失敗,就得從新去讀取最新的數據庫餘額。
經過上面這個例子能夠看出來,使用「樂觀鎖」機制,必須得知足:
(1)鎖服務要有遞增的版本號version (2)每次更新數據的時候都必須先判斷版本號對不對,而後再寫入新的版本號
如何一塊兒學習,有沒有免費資料?
對Java技術,架構技術感興趣的同窗,歡迎加QQ羣:681179158,一塊兒學習,相互討論。
羣內已經將知識體系整理好(源碼,筆記,PPT,學習視頻),歡迎加羣免費領取。
掃碼加羣:681179158 免費領取資料
咱們再來看一下如何基於「悲觀鎖」來實現:
悲觀鎖也叫做排它鎖,在Mysql中是基於 for update 來實現加鎖的,例如:
//鎖定的方法-僞代碼 public boolean lock(){ connection.setAutoCommit(false) for(){ result = select * from user where id = 100 for update; if(result){ //結果不爲空, //則說明獲取到了鎖 return true; } //沒有獲取到鎖,繼續獲取 sleep(1000); } return false; }
//釋放鎖-僞代碼 connection.commit();
上面的示例中,user表中,id是主鍵,經過 for update 操做,數據庫在查詢的時候就會給這條記錄加上排它鎖。
(須要注意的是,在InnoDB中只有字段加了索引的,纔會是行級鎖,否者是表級鎖,因此這個id字段要加索引)
當這條記錄加上排它鎖以後,其它線程是沒法操做這條記錄的。
那麼,這樣的話,咱們就能夠認爲得到了排它鎖的這個線程是擁有了分佈式鎖,而後就能夠執行咱們想要作的業務邏輯,當邏輯完成以後,再調用上述釋放鎖的語句便可。
基於Redis實現的鎖機制,主要是依賴redis自身的原子操做,例如:
SET user_key user_value NX PX 100
redis從2.6.12版本開始,SET命令才支持這些參數:
NX:只在在鍵不存在時,纔對鍵進行設置操做,SET key value NX 效果等同於 SETNX key value
PX millisecond:設置鍵的過時時間爲millisecond毫秒,當超過這個時間後,設置的鍵會自動失效
上述代碼示例是指:
當redis中不存在user_key這個鍵的時候,纔會去設置一個user_key鍵,而且給這個鍵的值設置爲 user_value,且這個鍵的存活時間爲100ms
爲何這個命令能夠幫咱們實現鎖機制呢?
由於這個命令是隻有在某個key不存在的時候,纔會執行成功。那麼當多個進程同時併發的去設置同一個key的時候,就永遠只會有一個進程成功。
當某個進程設置成功以後,就能夠去執行業務邏輯了,等業務邏輯執行完畢以後,再去進行解鎖。
解鎖很簡單,只須要刪除這個key就能夠了,不過刪除以前須要判斷,這個key對應的value是當初本身設置的那個。
另外,針對redis集羣模式的分佈式鎖,能夠採用redis的Redlock機制。
如何一塊兒學習,有沒有免費資料?
對Java技術,架構技術感興趣的同窗,歡迎加QQ羣:681179158,一塊兒學習,相互討論。
羣內已經將知識體系整理好(源碼,筆記,PPT,學習視頻),歡迎加羣免費領取。
掃碼加羣:681179158 免費領取資料
其實基於ZooKeeper,就是使用它的臨時有序節點來實現的分佈式鎖。
原理就是:當某客戶端要進行邏輯的加鎖時,就在zookeeper上的某個指定節點的目錄下,去生成一個惟一的臨時有序節點, 而後判斷本身是不是這些有序節點中序號最小的一個,若是是,則算是獲取了鎖。若是不是,則說明沒有獲取到鎖,那麼就須要在序列中找到比本身小的那個節點,並對其調用exist()方法,對其註冊事件監聽,當監聽到這個節點被刪除了,那就再去判斷一次本身當初建立的節點是否變成了序列中最小的。若是是,則獲取鎖,若是不是,則重複上述步驟。
當釋放鎖的時候,只需將這個臨時節點刪除便可。
(圖片來自網絡)
如圖,locker是一個持久節點,node_1/node_2/…/node_n 就是上面說的臨時節點,由客戶端client去建立的。
client_1/client_2/…/clien_n 都是想去獲取鎖的客戶端。以client_1爲例,它想去獲取分佈式鎖,則須要跑到locker下面去建立臨時節點(假如是node_1)建立完畢後,看一下本身的節點序號是不是locker下面最小的,若是是,則獲取了鎖。若是不是,則去找到比本身小的那個節點(假如是node_2),找到後,就監聽node_2,直到node_2被刪除,那麼就開始再次判斷本身的node_1是否是序列中最小的,若是是,則獲取鎖,若是還不是,則繼續找一下一個節點。
以上,就講完了爲何咱們須要分佈式鎖這個技術,以及分佈式鎖中常見的三種機制,歡迎你們一塊兒交流。