隨著現在分佈式越來越普遍,分佈式鎖也十分常用,這篇文章解釋了 使用zookeeper實現分佈式鎖,本次咱們說一下如何用Redis實現分佈式鎖和分佈限流。
Redis有個事務鎖,就是如下的命令,這個命令的含義是將一個value設置到一個key中,如果不存在將會賦值並且設置超時時間為30秒,如何這個key已經存在了,則不進行設置。
SET key value NX PX
30000
這個事務鎖很好的解決了兩個單獨的命令,一個設置set key value nx,即該key不存在的話將對其進行設置,另一個是expire key seconds,設置該key的超時時間。我們可以想一下,如果這兩個命令用程序單獨使用會存在什麼問題:
- 如果一個set key的命令設置了key,然後程序異常了,expire時間沒有設置,那麼這個key會一直鎖住。
- 如果一個set key時出現了異常,但是直接執行了expire,過了一會兒之後另一個進行set key,還沒怎麼執行代碼,結果key過期了,別的線程也進入了鎖。
還有很多出問題的可能點,這裡我們就不討論了,下面咱們來看看如何實現吧。
本文使用的Spring Boot 2.x + Spring data redis + Swagger +lombok + AOP + lua腳本。在實現的過程中遇到了很多問題,都一一解決實現了。
依賴的POM文件如下:
使用了兩個lua腳本,一個用於執行lock,另一個執行unlock。
咱們簡單看一下,lock腳本就是採用Redis事務執行的set nx px命令,其實還有set nx ex命令,這個ex命令是採用秒的方式進行設置過期時間,這個px是採用毫秒的方式設置過期時間。
value需要使用一個唯一的值,這個值在解鎖的時候需要判斷是否一致,如果一致的話就進行解鎖。這個也是官方推薦的方法。另外在lock的地方我設置了一個result,用於輸出測試時的結果,這樣就可以結合程序去進行debug了。
來看下代碼,主要寫了兩個方法,一個是用與鎖另外一個是用於結解鎖。這塊需要注意的是使用RedisTemplate,這塊意味著key和value一定都是String的,我在使用的過程中就出現了一些錯誤。首先初始化兩個腳本到程序中,然後調用執行腳本。
還有一個就是腳本定義的地方需要注意,返回的結果集一定是Long, Boolean,List, 一個反序列化的值。這塊要注意。
好了,這塊就寫好了,然後寫好controller類準備測試。
我也寫了一個測試類,用於測試和輸出結果, 使用100個線程,然後鎖的時間設置10秒,controller裡邊需要休眠3秒模擬業務執行。
獲取鎖的地方就會執行do business logic, 然後會有部分線程獲取到鎖並執行業務,執行完業務的就會釋放鎖。
分佈式鎖就實現好了,接下來實現分佈式限流。先看一下limit的lua腳本,需要給腳本傳兩個值,一個值是限流的key,一個值是限流的數量。
獲取當前key,然後判斷其值是否為nil,如果為nil的話需要賦值為0,然後進行加1並且和limit進行比對,如果大於limt即返回0,說明限流了,如果小於limit則需要使用Redis的INCRBY key 1,就是將key進行加1命令。並且設置超時時間,超時時間是秒,並且如果有需要的話這個秒也是可以用參數進行設置。
執行limit的腳本和執行lock的腳本類似。
接下來咱們寫一個限流注解,並且設置註解的key和限流的大小:
然後對註解進行切面,在切面中判斷是否超過limit,如果超過limit的時候就需要拋出異常exceeded limit,否則正常執行。
因為有拋出異常,這裡我弄了一個統一的controller錯誤處理,如果controller出現Exception的時候都需要走這塊異常。如果是正常的RunTimeException的時候獲取一下,否則將異常獲取一下並且輸出。
好了,接下來將註解寫到自定義的controller上,limit的大小為10,也就是10秒鐘內限制10次訪問。
也是來一段Test方法來跑,老方式100個線程開始跑,只有10次,其他的都是limit。沒有問題。
總結一下,這次實現採用了使用lua腳本和Redis實現了鎖和限流,但是真實使用的時候還需要多測試,另外如果此次Redis也是採用的單機實現方法,使用集群的時候可能需要改造一下。
關於鎖這塊其實Reids自己也實現了RedLock, java實現的版本Redission。也有很多公司使用了,功能非常強大。各種場景下都用到了。
閱讀更多 java互聯網架構 的文章