搞懂Java分佈式鎖實現看這篇文章就對了

 

前言:node

隨着微處理機技術的發展,人們只需花幾百美圓就能買到一個CPU芯片,這個芯片每秒鐘執行的指令比80年代最大的大型機的處理機每秒鐘所執行的指令還多。若是你願意付出兩倍的價錢,將獲得一樣的CPU,但它卻以更高的時鐘速率運行。所以,最節約成本的辦法一般是在一個系統中使用集中在一塊兒的大量的廉價CPU。因此,傾向於分佈式系統的主要緣由是它能夠潛在地獲得比單個的大型集中式系統好得多的性價比。實際上,分佈式系統是經過較低廉的價格來實現類似的性能的。redis

隨着互聯網的興起,愈來愈多的人使用者互聯網產品。通常互聯網系統都是分佈式部署的,分佈式部署確實能帶來性能和效率上的提高,提高效率的同事,咱們還須要注意,保證一個分佈式環境下數據一致性的問題。數據庫

分佈式鎖簡述性能優化

在單機時代,雖然不存在分佈式鎖,但也會面臨資源互斥的狀況,只不過在單機的狀況下,若是有多個線程要同時訪問某個共享資源的時候,咱們能夠採用線程間加鎖的機制,即當某個線程獲取到這個資源後,就須要對這個資源進行加鎖,當使用完資源以後,再解鎖,其它線程就能夠接着使用了。例如,在JAVA中,甚至專門提供了一些處理鎖機制的一些API(synchronize/Lock等)。架構

可是到了分佈式系統的時代,這種線程之間的鎖機制,就沒做用了,系統可能會有多份而且部署在不一樣的機器上,這些資源已經不是在線程之間共享了,而是屬於進程之間共享的資源。所以,爲了解決這個問題,「分佈式鎖」就強勢登場了。併發

分佈式鎖是控制分佈式系統之間同步訪問共享資源的一種方式。在分佈式系統中,經常須要協調他們的動做。若是不一樣的系統或是同一個系統的不一樣主機之間共享了一個或一組資源,那麼訪問這些資源的時候,每每須要互斥來防止彼此干擾來保證一致性,在這種狀況下,便須要使用到分佈式鎖。分佈式

在分佈式系統中,經常須要協調他們的動做。若是不一樣的系統或是同一個系統的不一樣主機之間共享了一個或一組資源,那麼訪問這些資源的時候,每每須要互斥來防止彼此干擾來保證一致性,這個時候,便須要使用到分佈式鎖。微服務

分佈式鎖要知足哪些要求呢?高併發

  • 排他性: 在同一時間只會有一個客戶端能獲取到鎖,其它客戶端沒法同時獲取
  • 避免死鎖: 這把鎖在一段有限的時間以後,必定會被釋放(正常釋放或異常釋放)
  • 高可用: 獲取或釋放鎖的機制必須高可用且性能佳
  • ...

目前相對主流的有三種,從實現的複雜度上來看,從上往下難度依次增長:源碼分析

  • 數據庫(MySQL)
  • Redis
  • ZooKeeper

v 基於數據庫實現

基於數據庫來作分佈式鎖的話,一般有兩種作法:

  • 基於數據庫的樂觀鎖
  • 基於數據庫的悲觀鎖

樂觀鎖

樂觀鎖的特色先進行業務操做,不到萬不得已不去拿鎖。即「樂觀」的認爲拿鎖多半是會成功的,所以在進行完業務操做須要實際更新數據的最後一步再去拿一下鎖就好。

樂觀鎖機制其實就是在數據庫表中引入一個版本號(version)字段來實現的。當咱們要從數據庫中讀取數據的時候,同時把這個version字段也讀出來,若是要對讀出來的數據進行更新後寫回數據庫,則須要將version加1,同時將新的數據與新的version更新到數據表中,且必須在更新的時候同時檢查目前數據庫裏version值是否是以前的那個version,若是是,則正常更新。若是不是,則更新失敗,說明在這個過程當中有其它的進程去更新過數據了。

看圖敘事。模擬實戰場景。

 

如上圖,故事男主人公(如下簡稱男主)打算去ATM機取3000元,故事女主人公(如下簡稱女主)則要在某寶買買買,買個包須要3000元,帳戶的餘額是5000元。若是沒有采用鎖的話,在兩人同時取款和買買買,可能會出現合計消費了6000,致使帳戶餘額異常。因此須要用到鎖的機制,當男主女主甚至更多小主同時消費時,除了讀取到6000的帳戶餘額外,還須要讀取到當前的版本號version=1,等先行消費成功的主人公(不管誰先消費)去出發修改帳戶餘額的同時,會觸發version=version+1,即version=2。那麼其餘人使用未更新的version(1)去更新帳戶餘額時就會發現版本號不對,就會致使本次更新失敗,就得從新去讀取最新帳戶餘額以及版本號。

樂觀鎖遵循的兩點法則:

  • 鎖服務要有遞增的版本號version
  • 每次更新數據的時候都必須先判斷版本號對不對,而後再寫入新的版本號

悲觀鎖

悲觀鎖的特色是先獲取鎖,再進行業務操做,即「悲觀」的認爲獲取鎖是很是有可能失敗的,所以要先確保獲取鎖成功再進行業務操做。

一般所說的「一鎖二查三更新」即指的是使用悲觀鎖。一般來說在數據庫上的悲觀鎖須要數據庫自己提供支持,即經過經常使用的 select ... for update 操做來實現悲觀鎖。當數據庫執行 select for update 時會獲取被 select 中的數據行的行鎖,所以其餘併發執行的 select for update 若是試圖選中同一行則會發生排斥(須要等待行鎖被釋放),所以達到鎖的效果。 select for update 獲取的行鎖會在當前事務結束時自動釋放,所以必須在事務中使用。

示例:

 
/** * 消費之後更新銀行餘額 * @param bankId 銀行卡號 * @param cost 消費金額 * @return */ public boolean consume(Long bankId, Integer cost){ //先鎖定銀行帳戶 BankAccount product = query("SELECT * FROM bank_account WHERE bank_id=#{bankId} FOR UPDATE", bankId); if (product.getNumber() > 0) { int updateCnt = update("UPDATE tb_product_stock SET number=#{cost} WHERE product_id=#{productId}", cost, bankId); if(updateCnt > 0){ //更新庫存成功 return true; } } return false; }

樂觀鎖與悲觀鎖的區別

樂觀鎖的思路通常是表中增長版本字段,更新時where語句中增長版本的判斷,算是一種CAS(Compare And Swep)操做,銀行消費場景中version起到了版本控制的做用( AND version=#{version} )。

悲觀鎖之因此是悲觀,在於他認爲本次操做會發生併發衝突,因此一開始就對銀行帳戶加上鎖( SELECT ... FOR UPDATE ),而後就能夠安心的作判斷和更新,由於這時候不會有別人更新帳戶餘額。

v 基於Redis實現

基於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 (可能會被牆)機制。

v 基於ZooKeeper實現

其實基於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是否是序列中最小的,若是是,則獲取鎖,若是還不是,則繼續找一下一個節點。

總結:

分佈式鎖有不少種,開篇說的"相對主流的有三種"只是針對我所遇到的。分佈式鎖將來確定是變幻無窮的,不管你身處一個什麼樣的公司,最開始的工做可能都得儘量的從簡單的作起。但願你們能根據所在公司業務場景,選擇適合所在項目的方案。

順便在此給你們推薦一個Java架構方面的交流學習羣:698581634,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系,主要針對Java開發人員提高本身,突破瓶頸,相信你來學習,會有提高和收穫。在這個羣裏會有你須要的內容  朋友們請抓緊時間加入進來吧。

相關文章
相關標籤/搜索