如何設計一款高性能分布式鎖,實現數據的安全訪問?

2022年07月12日05:30:10 熱門 1603

隨着互聯網技術的飛速發展,分布式已經成為一個繞不開的話題,分布式環境下,“高並發訪問共享資源”的場景並不少見,帶來的問題也顯⽽易見: 共享資源在訪問前後出現了數據不一致或非預期結果!!!

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

01

什麼是分布式鎖?

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

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

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

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

如何設計一款高性能分布式鎖,實現數據的安全訪問? - 天天要聞

互斥性: 同一時間,保證共享資源只能被一個客戶端的一個線程能訪問,具有排他性。

防死鎖: 鎖在一段時間後,一定會被釋放(正常釋放或異常釋放)。

高可用: 獲取鎖的機制必須高可用,性能佳。

阻塞鎖(可選): 當前資源已被加鎖,其他客戶端或者線程是阻塞等待,還是立即返回。

可重入(可選): 當前鎖的持有者是否能再次進入。

公平性(可選): 加鎖的順序和請求加鎖的順序是一致,還是隨機搶鎖。

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

分布式鎖就是用來 解決高並發訪問導致數據不一致的問題 ,這裡列舉幾種常見的場景。

多用戶修改數據,造成數據不準確: 多個請求對同一條數據同時進行修改,導致數據不準確。比如 “ 下單減庫存 ” 、 “ 互聯網秒殺 ” 、 “ 搶紅包 ” 、 “ 搶票 ” 、 “ 搶優惠券 ” 、 “ 互聯網選號 ” 、“ 轉賬 ” 等。

多次請求,數據重複: 請求結果暫未返回時, 進行多次操作或重試, 產生多個相同的請求,不加鎖的情況下成功,會產生很多重複記錄。

分布式協調: 分布式環境下,多台機器都可以執行任務,每次只能一台機器執行,也可以用分布式鎖來做標記,只有獲取到鎖的機器可以執行。

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

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

“ 分布式鎖 ” 其實是一種解決方案,並非專有組件或者類,實現這一解決方案仍舊需要額外的組件或者中間件來輔助,甚至某些情況下,需要藉助數據庫級別的方式來實現。

如何設計一款高性能分布式鎖,實現數據的安全訪問? - 天天要聞

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

基於數據庫: 藉助數據庫鎖實現,實現簡單,性能是最大問題。( 不推薦 )

基於 Redis : CAP 模型屬於 AP ,無一致性算法,速度快。( 高性能場景推薦 )

基於 Zookeeper : CAP 模型屬於 CP ,可靠性高,性能比 Redis 差一些。( 高可靠場景推薦)

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

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

02

高並發場景下分布式鎖如何設計?

因為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 集群路由、腳本執行失敗重試等等。

順便說一句,完整版“lock-sdk”已發布在公司maven倉庫,可以直接使用。Redis高性能版內部實現了CashCloud接入,註解方式使用鎖,後期也會實現Zookeeper高可靠版本。

寫在最後的話

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

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

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

希望以上內容能對有需要的人有所幫助

熱門分類資訊推薦

曾小賢的上司Lisa榕,現實中不僅才貌雙全,還嫁給了CEO - 天天要聞

曾小賢的上司Lisa榕,現實中不僅才貌雙全,還嫁給了CEO

曾小賢的上司Lisa榕,現實中不僅才貌雙全,還嫁給了CEO雖然說《愛情公寓》這部劇在劇情上充滿了爭議,但是一定程度上,這部劇也是很多人的回憶,是伴隨了一代人的青春回憶,而且劇中的很多角色都成為了經典,他們的口頭禪也一直被拿來玩兒梗。
Lisa榕做主持多年沒紅,被陳赫拉進愛情公寓爆紅,如今怎樣了 - 天天要聞

Lisa榕做主持多年沒紅,被陳赫拉進愛情公寓爆紅,如今怎樣了

談到《愛情公寓》這部火爆一時的歡樂喜劇,大家肯定都不陌生。不知道大家是否還記得《愛情公寓》中那個把曾小賢治得服服帖帖的女上司Lisa榕,現實中的她名叫榕榕,和劇中的形象也判若兩人。1981年出生在遼寧瀋陽的榕榕,畢業於上海戲劇學院,後來成為了上海東方傳媒集團有限公司的一名主持人。