1. 程式人生 > >手撕redis分散式鎖,隔壁張小帥都看懂了!

手撕redis分散式鎖,隔壁張小帥都看懂了!

### 前言 上一篇老貓和小夥伴們分享了為什麼要使用分散式鎖以及分散式鎖的實現思路原理,目前我們主要採用第三方的元件作為分散式鎖的工具。上一篇運用了Mysql中的select ...for update實現了分散式鎖,但是我們說這種實現方式並不常用,因為當大併發量的時候,會給資料庫帶來比較大的壓力。當然也有小夥伴給老貓留言說“ 在quartz的叢集模式中,就是使用了基於mysql的分散式鎖,select for update ”。沒錯,其實quartz的叢集模式中,任務執行的節點個數是可預知的,而且沒有那麼大的量級,所以是沒有問題的。但是如果像千萬級別的併發秒殺場景的情況下,那麼這種方案其實是不可行的。因為mysql操作是需要IO的,IO的速度比記憶體速度慢,因此mysql如果在那種場景下使用的話是會存在系統瓶頸的。所以本篇就和小夥伴們分享基於記憶體操作的比較常用的分散式鎖——redis分散式鎖。 ### 手擼Redis分散式鎖 #### 實現原理 redis分散式鎖實現原理其實也是比較簡單的,主要是依賴於redis的 set nx命令,我們來看一下完整的設定redis的命令:“Set resource_name my_random_value NX PX 30000”。看到這串命令,瞭解redis的小夥伴應該都看得懂這條命令是在redis中存入一個帶有過期時間的值。具體上述設值語句解釋如下: 1. resource_name:資源名稱,可以根據不同的業務區分不同的鎖。(其實就是對應我們上一篇myql鎖中的business_code)。 2. my_random_value:隨機值,每個執行緒的隨機值都不相同,主要用於釋放鎖的時候用來校驗。 3. NX:key不存在的時候設定成功,key存在則設定不成功。 4. PX:自動失效時間,如果出現異常情況,鎖可以過期實現,因此達到了自動釋放。 那麼為什麼可以使用這個思路呢?其實很簡單,主要就是利用了set nx的原子性,在多個執行緒併發執行時,只有一個執行緒可以設定成功,如果設定成功,那麼就代表著獲得了鎖,就可以執行後續的業務。如果出現了異常,過了鎖的有效期,鎖會自動釋放,釋放鎖主要採用了redis的delete命令,釋放鎖之前會校驗當前redis儲存的隨機數,只有當前的隨機數和儲存的隨機數一致的時候才允許釋放。具體的redis的刪除,我們可以通過lua指令碼進行刪除,具體Lua指令碼如下: ```lua if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end ``` 那麼我們為什麼要採用這種方式釋放鎖呢?其實使用這種方式釋放鎖可以避免刪除別的客戶端獲取成功的鎖 。 如下圖: ![redis釋放鎖](https://img2020.cnblogs.com/blog/2200669/202101/2200669-20210109212811578-619703094.png) 客戶端A取得資源鎖,但是緊接著被一個其他操作阻塞了,當客戶端A執行完畢其他操作後要釋放鎖時,原來的鎖早已超時並且被Redis自動釋放,並且在這期間資源鎖又被客戶端B再次獲取到。如果僅使用DEL命令將key刪除,那麼這種情況就會把客戶端B的鎖給刪除掉。使用Lua指令碼就不會存在這種情況,因為指令碼僅會刪除value等於客戶端A的value的key(value相當於客戶端的一個簽名)(說明:其實這些例子在redis的官網都有介紹)。 #### 程式碼實現方式 老貓對redis鎖機制進行了相關的抽取,並且封裝成了工具類,核心工具類程式碼如下: ```java /** * @author [email protected] * @date 2021/1/7 22:36 * 公眾號“程式設計師老貓” */ @Service public class RedisLockUtil { @Autowired private RedisTemplate redisTemplate; private String value = UUID.randomUUID().toString(); public Boolean lock(String key){ Redis