相關知識
分布式鎖
分布式鎖是控制分布式系統之間同步通路共享資源的一種方式,在分布式系統中,如果不同的應用之間共享一個或一組資源,那麼通路這些資源的時候,往往需要互斥來防止彼此幹擾來保證一緻性,在這種情況下,便需要使用到分布式鎖。
- 互斥性。在分布式環境下,同一時間隻有一個用戶端能持有鎖。
- 具備鎖失效機制,防止死鎖。例如鎖的持有者在持有鎖期間崩潰而沒有主動解鎖,鎖需要在規定時間後自動失效,以保證後續可用。
- 具備可重入性,防止死鎖。
- 解鈴還須系鈴人。釋放鎖與加鎖應該為相同用戶端,不能把别人加的鎖給解了。
Redis 分布式鎖實作原理
實作原理可參考 http://www.redis.cn/topics/distlock.html。
Redis 是單線程的,是以 Redis 指令具有原子性,Redis 提供了以下幾個指令
- setnx,意思是待建立的鍵如果已存在,則建立失敗,否則建立成功,實作互斥性。
- expire,給鍵加上過期時間,實作自動失效機制
- set key value [EX|PX] [NX],可以将上面兩個指令的動作,在這一個指令實作,其中,EX|PX 是過期時間,EX 使用秒,PX 使用毫秒,NX 表示 setnx 的意思
因為鎖的實作中擁有比較、加鎖等一系列操作,為保證原子性,需要引入 Lua 腳本
加鎖流程:
釋放鎖流程
目标
整合 Redis 實作分布式鎖
操作步驟
添加依賴
依賴以及配置見上章https://blog.csdn.net/qq_34479912/article/details/105719636
編碼
鎖是針對某個資源的狀态,保證其通路的互斥性,在實際使用當中,這個狀态一般是一個字元串。使用 Redis 實作鎖,主要是将狀态放到 Redis 當中,利用其原子性,當其他線程通路時,如果 Redis 中已經存在這個狀态,就不允許之後的一些操作。spring boot使用Redis的操作主要是通過RedisTemplate(或StringRedisTemplate )來實作。
1.1 将鎖狀态放入 Redis:
redisTemplate.opsForValue().setIfAbsent("lockkey", "value"); // setIfAbsent如果鍵不存在則新增,存在則不改變已經有的值。
1.2 設定鎖的過期時間
redisTemplate.expire("lockkey", 30000, TimeUnit.MILLISECONDS);
1.3 删除/解鎖
redisTemplate.delete("lockkey");
這麼就是簡單實作,但是1.1和1.2這麼做,這兩步違背了原子性,也就是一旦鎖被建立,而沒有設定過期時間,則鎖會一直存在。
1.4 擷取鎖
redisTemplate.opsForValue().get("lockkey");
1.5 解決方案
spring data的 RedisTemplate 當中并沒有這樣的方法。但是在jedis當中是有這種原子操作的方法的,需要通過 RedisTemplate 的 execute 方法擷取到jedis裡操作指令的對象.
String result = template.execute(new RedisCallback() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.set(key, "鎖定的資源", "NX", "PX", 3000); } });
注意: Redis 從2.6.12版本開始 set 指令支援 NX 、 PX 這些參數來達到 setnx 、 setex 、 psetex 指令的效果,文檔參見: SET — Redis 指令參考
NX: 表示隻有當鎖定資源不存在的時候才能 SET 成功。利用 Redis 的原子性,保證了隻有第一個請求的線程才能獲得鎖,而之後的所有線程在鎖定資源被釋放之前都不能獲得鎖。
鎖的進階
模拟一個比較常見的秒殺場景,這時候就需要用到鎖。
建立RedisLockHelper
這個是Redis加鎖和解鎖的工具類,裡面使用的主要是兩個指令,SETNX和GETSET。
SETNX指令 将key設定值為value,如果key不存在,這種情況下等同SET指令。 當key存在時,什麼也不做
GETSET指令 先查詢出原來的值,值不存在就傳回nil。然後再設定值 對應的Java方法在代碼中提示了。 注意一點的是,Redis是單線程的!是以在執行GETSET和SETNX不會存在并發的情況。
建立Controller模拟秒殺場景
源碼位址
本章源碼 :https://github.com/caiyuanzi-song/boot.git
參考
https://www.cnblogs.com/toutou/archive/2019/02/11/redis_lock.html
擴充
Redis 相關資料
- spring-data-redis文檔: https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/#new-in-2.0.0
- Redis 文檔: https://redis.io/documentation
- Redis 中文文檔: http://www.redis.cn/commands.html