分佈式鎖的簡單理解

分佈式鎖

場景一

同一個應用用部署在多機器,用於支撐高併發訪問以下代碼有什麼問題?

@Transactional
public boolean doOrder(Integer productId , Integer buySize ) {
    //獲取當前的產品庫存數量
    Product product = mapper.selectByPrimaryKey(id); 
    if(product.getStock() >= buySize){
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("id", id);
        map.put("stock", buySize); 
        mapper.updateStock(map); 
        System.out.println("庫存充足,購買成功");
    }else{
        System.out.println("庫存不足,購買失敗"); return false;
    }
    return true;
}


悲觀鎖

顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,所以每次在拿數據   的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據  庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作   之前先上鎖。

 

基於Redis實現

實現原理:  Redis爲單進程單線程模式,採用隊列模式將併發訪問變成串行訪問,且多客戶端對Redis的連接並不存在競爭關係。redisSETNX命令可以方便的實現分佈  式鎖。

SETNX命令SET if Not eXists 語法: SETNX key value 功能: 當且僅當 key 存在,將 key 的值設爲 value ,並返回1;若給定的 key 已經存在,則 SETNX 不做任何動作,並返回0

 

基於zookeeper實現

獲得鎖:通過構建一個目錄,當葉節點能創建成,則認爲獲取到鎖,因爲一旦一個節  點被某個會話創建,其它會話 再次創建個節點時,將會拋出異常,比如目錄爲

/exclusiveLock/lock

  • 釋放鎖: 刪除節點或者會話失效

 

場景二

  1. 假設噹噹網上用戶下單買了本書,這時數據庫中有條訂單號爲001的訂單,其中有個

status字段是有效’,表示該訂單是有效的;

  1. 後臺管理人員查詢到這條001的訂單,並且看到狀態是有效的
  2. 用戶發現下單的時候下錯了,於是撤銷訂單,假設運行這樣一條SQL: update order_table set status = ‘取消where order_id = 001;
  3. 後臺管理人員由於在b這步看到狀態有效的,這時,雖然用戶在c這步已經撤銷了訂  單,可是管理人員並未刷新界面,看到的訂單狀態還是有效的,於是點擊發貨

鈕,將該訂單發到物流部門,同時運行類似如下SQL,將訂單狀態改成已發貨:update order_table set status = ‘已發貨’ where order_id = 001

 

樂觀鎖

 

顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,  但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號  等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類  似於write_condition機制的其實都是提供的樂觀鎖。

 

基於數據版本( Version )記錄機制實現

何謂數據版本?即爲數據增加一個版本標識,在基於數據庫表的版本解決方案中,一  般是通過爲數據庫表增加一個 “version字段來實現。讀取出數據時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對  應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,  則予以更新,否則認爲是過期數據。

示例

對於上面修改用戶帳戶信息的例子而言,假設數據庫中帳戶信息表中有一個version  字段,當前值爲 1 ;而當前帳戶餘額字段( balance $100 

  1. 操作員 A 此時將其讀出( version=1 ),並從其帳戶餘額中扣除$50
  2. 在操作員 A 操作的過程中,操作員 B 也讀入此用戶信息( version=1 ),並從其帳戶餘額中扣除 ( 100­$20 )。
  3. 操作員 A 完成了修改工作,將數據版本號加一( version=2 ),連同帳戶扣除後餘額( balance=$50 ),提交至數據庫更新,此時由於提交數據版本大於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2 
  4. 操作員 B 完成了操作,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操作員 B 提交的數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不滿足 提交版本必須大於記錄當前版本才能執行更新 的樂觀鎖策略,因此,操作員 B 的提交被駁回。

這樣,就避免了操作員 B 用基於 version=1 的舊數據修改的結果覆蓋操作員 A 的操作結果的可能。

從上面的例子可以看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷

需要注意的是,樂觀鎖機制往往基於系統中的數據存儲邏輯,因此也具備一定的侷限性,如   在上例中,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶餘額更新操作不受   我們系統的控制,因此可能會造成髒數據被更新到數據庫中。在系統設計階段,我們應該充   分考慮到這些情況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程中實   現,對外只開放基於此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開。

 

優缺點

​​​​​​​兩種鎖各有優缺點,不可認爲一種好於另一種,像樂觀鎖適用於寫比較少的情況下,  即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。  但如果經常產生衝突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這  種情況下用悲觀鎖就比較合適。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章