天天看點

如何設計一款高性能分布式鎖,實作資料的安全通路?

作者:dbaplus社群

随着網際網路技術的飛速發展,分布式已經成為一個繞不開的話題,分布式環境下,“高并發通路共享資源”的場景并不少見,帶來的問題也顯⽽易見:共享資源在通路前後出現了資料不一緻或非預期結果!

單體時代可以⽤JVM提供的ReentrantLock或者Synchronized解決,分布式環境下,JVM就有點力不不從心了。于是乎,“分布式鎖”便出現了。

一、什麼是分布式鎖?

在計算機科學中,鎖(lock)與互斥(mutex)是一種同步機制,用于在許多線程執行時對資源的限制。

分布式鎖可以了解為,控制分布式系統有序的去對共享資源進行操作,通過互斥來保持一緻性。

1、分布式鎖應具備哪些特性?

分布式鎖是多服務共享鎖,在分布式的部署環境下,通過鎖機制來讓用戶端互斥的對共享資源進行通路,應該具備以下特性。

如何設計一款高性能分布式鎖,實作資料的安全通路?
  • 互斥性:同一時間,保證共享資源隻能被一個用戶端的一個線程能通路,具有排他性。
  • 防死鎖:鎖在一段時間後,一定會被釋放(正常釋放或異常釋放)。
  • 高可用:擷取鎖的機制必須高可用,性能佳。
  • 阻塞鎖(可選):目前資源已被加鎖,其他用戶端或者線程是阻塞等待,還是立即傳回。
  • 可重入(可選):目前鎖的持有者是否能再次進入。
  • 公平性(可選):加鎖的順序和請求加鎖的順序是一緻,還是随機搶鎖。

2、分布式鎖可以解決哪些場景的問題?

分布式鎖就是用來解決高并發通路導緻資料不一緻的問題,這裡列舉幾種常見的場景:

  • 多使用者修改資料,造成資料不準确:多個請求對同一條資料同時進行修改,導緻資料不準确。比如“下單減庫存”、“網際網路秒殺”、“搶紅包”、“搶票”、“搶優惠券”、“網際網路選号”、“轉賬”等。
  • 多次請求,資料重複:請求結果暫未傳回時,進行多次操作或重試,産生多個相同的請求,不加鎖的情況下成功,會産生很多重複記錄。
  • 分布式協調:分布式環境下,多台機器都可以執行任務,每次隻能一台機器執行,也可以用分布式鎖來做标記,隻有擷取到鎖的機器可以執行。

3、分布式鎖有哪些實作方式?

關于鎖,Java提供了種類豐富的鎖,每種鎖因其特性的不同,在适當的場景下能夠展現出非常高的效率。

“分布式鎖”其實是一種解決方案,并非專有元件或者類,實作這一解決方案仍舊需要額外的元件或者中間件來輔助,甚至某些情況下,需要借助資料庫級别的方式來實作。

如何設計一款高性能分布式鎖,實作資料的安全通路?

關于分布式鎖的實作方案,在業界流行的有三種:

  • 基于資料庫:借助資料庫鎖實作,實作簡單,性能是最大問題。(不推薦)
  • 基于Redis:CAP模型屬于AP,無一緻性算法,速度快。(高性能場景推薦)
  • 基于Zookeeper:CAP模型屬于CP,可靠性高,性能比Redis差一些。(高可靠場景推薦)

另外,還有使用etcd、consul來實作的。

到這裡,我們已經對分布式鎖的特點、使用場景、實作方式有了大緻的了解。那麼,一款高性能分布式鎖到底應該如何設計?請繼續往下看。

二、高并發場景下分布式鎖如何設計?

因為Redis出色的性能,在高并發環境中,使用最多的是Redis方案,實作最複雜,最容易出問題的也是Redis方案。

接下來,用Redis來實作一個庫存加分鎖的列子,對分布式鎖的設計原理和思路進行闡述。

  • 需求場景:假設庫存有100件商品,通過網際網路秒殺下單,要求搶完的同時不能超賣。
  • 分布式模拟:啟用2個服務,來模拟分布式環境,前端用Nginx分發請求。
  • 并發工具:使用JMeter并發模拟多個使用者并發請求。

1、無鎖減庫存

我們先來看一下無鎖的情況,下單減庫存會存在什麼問題?具體代碼如下:

如何設計一款高性能分布式鎖,實作資料的安全通路?

并發請求模拟:

  • 測試計劃->添加線程組(配置線程屬性)
  • 線程組->添加->Sampler ->HTTP請求(配置http請求位址)
  • HTTP請求->添加監聽器(圖形結果、檢視結果樹)
  • 選項-> Log Viewer (打開日志)
如何設計一款高性能分布式鎖,實作資料的安全通路?

執行結果如下:

問題很明顯,當庫存為1時,還成功了3個訂單,這結果并不是我們所期望的。

這是因為,分布式環境下,當隻有1個庫存時候,同時有3個線程讀取到了該庫存,完成了下單。這種多使用者通路導緻資料不準确的問題,就可以用分布式鎖來解決。

接下來,我們看看用Redis怎麼實作分布式鎖。

2、分布式鎖實作(初級版)

根據前面介紹的,分布式鎖,必須具備下面三個特性:

  • 互斥性:隻有擷取到鎖的線程才能通路。
  • 防死鎖:設定過期自動删除來實作解釋失敗導緻的死鎖。
  • 高可用:通過Redis Cluster的高可用來保證。

實作思路很簡單:通路庫存前,往Redis寫入一個鎖标志,通路結束删除鎖,隻有拿到鎖的才可以通路。

  • 設定過期時間來清理未被成功删除的鎖。
  • 設定加鎖人的身份辨別,防止被他人誤删。

Redis提供了豐富的指令操作功能,JAVA可以用RedisTemplate操作,代碼如下:

如何設計一款高性能分布式鎖,實作資料的安全通路?

再看⼀下結果:

如何設計一款高性能分布式鎖,實作資料的安全通路?

執行結果正常,到這裡,一個簡單分布式鎖就完成了。作為一個思路嚴謹的程式員,你可能還有諸多疑問:如果設定鎖成功,設定過期時間失敗了怎麼辦?如果過期時間到了,業務沒執行完怎麼辦?如果沒擷取到鎖,想等待鎖空閑再擷取,該怎麼實作?如果加鎖方法調用了其他方法,其他方法又調用加鎖方法,需多次進入該鎖,怎麼辦?

生産級使用,還需要實作:原子操作、續期、阻塞擷取、支援重入。

具體實作方法,請接着往下看。

如何設計一款高性能分布式鎖,實作資料的安全通路?

3、分布式鎖實作(進階版)

基于上面的問題,你也許想到了解決方案,比如:

  • 原子操作:可以通過Redis提供的Lua腳本功能來實作。
  • 續期:可以用異步線程自動續期,或者顯示調用續期方法。
  • 阻塞擷取:擷取鎖時設定等待時間,内部用循環自旋擷取鎖,直到逾時。
  • 重入:可以通過Redis Hash結構存儲,同時記錄key和value,每次進入value+1。

簡單介紹一下Lua腳本:

Redis Lua腳本:從redis 2.6.0推出了腳本功能,允許開發者用Lua語言編寫腳本,傳到Redis中執行。使用腳本好處:

  • 減少網絡開銷
  • 原子操作
  • 替代Redis的事物功能

接下來,我們分析一下加鎖、重入、解鎖的完整流程。

① 加鎖(續期)原理

如何設計一款高性能分布式鎖,實作資料的安全通路?

② 重入原理

  • 資料結構類似Java的Map <key,Map<key1,value>>類型,這裡key為鎖名稱,key1為用戶端資訊,value為重入次數。
  • 資料結構設計:<工程名稱+keyName,<hostaddress+uuid:線程ID,重入次數>>
  • 每重入一次,value就+1。
如何設計一款高性能分布式鎖,實作資料的安全通路?

③ 解鎖原理

  • 解鎖時,先判斷線程資訊(隻能操作目前線程的鎖),再将加鎖次數減1,當次數為0就删除鎖。
如何設計一款高性能分布式鎖,實作資料的安全通路?
如何設計一款高性能分布式鎖,實作資料的安全通路?

④ 加鎖和重入的Lua腳本

如何設計一款高性能分布式鎖,實作資料的安全通路?

Redis指令解釋:

  • EXISTS key:檢查給定 key 是否存在,存在傳回 1 ,否則傳回0 。
  • HSET key field value:将哈希表 key 中的域 field 的值設為 value 。
  • PEXPIRE key milliseconds:以毫秒為機關設定 key 的生存時間。
  • HEXISTS key field:檢視哈希表 key 中,給定域 field 是否存在。
  • HINCRBY key field increment:為哈希表 key 中的域 field 的值加上增量 increment 。
  • PTTL key:以毫秒為機關傳回 key 的剩餘生存時間。

⑤ 解鎖Lua腳本

如何設計一款高性能分布式鎖,實作資料的安全通路?

⑥ 腳本執行

執行Lua腳本,可以通過下面兩個方法(一次加載,多次執行)。

String hash = redisCluster.scriptLoad(script, key);

Object result = redisCluster.evalsha(hash, keys, args);

實作了上面這些功能,一個企業級高可用分布式鎖基本就完成了。

當然,在實作過程,還需要考慮很多細節問題,比如:腳本加載失敗重試、Redis叢集路由、腳本執行失敗重試等等。

三、寫在最後

本文介紹了分布式鎖特性、應用場景、以及實作方式,并以一個基于Redis設計分布式鎖的例子,介紹了分布式鎖的設計原理和思路,希望幫助大家對分布式鎖有一個更新的認識。

Redis實作分布式鎖隻是其中一種方案,也不能保證100%的一緻性,比如Redis叢集Master加鎖成功,還沒來得及同步到Slave節點,Master就挂了,這種場景也會出現資料不一緻的問題。如果對可靠性有更高要求,可以選擇Zookeeper實作方案。再比如,網際網路秒殺場景僅僅基于一個分布式鎖也不能完全扛得住,可能需要引入分段庫存鎖機制來實作。

任何技術都不是萬能的,沒有哪一種技術方案能解決所有業務場景的問題,希望大家根據業務場景選擇合适的技術方案!

作者丨沙漠鷹

來源丨八戒技術團隊

dbaplus社群歡迎廣大技術人員投稿,投稿郵箱:[email protected]

關于我們

dbaplus社群是圍繞Database、BigData、AIOps的企業級專業社群。資深大咖、技術幹貨,每天精品原創文章推送,每周線上技術分享,每月線下技術沙龍,每季度Gdevops&DAMS行業大會。

關注公衆号【dbaplus社群】,擷取更多原創技術文章和精選工具下載下傳

繼續閱讀