一、引入依賴
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.0</version></dependency>
二、配置類
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @Author ZGM* @DateTime 2023/8/15* @description*/
@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置,Config config = new Config();//單例模式config.useSingleServer().setAddress("redis://127.0.0.1:6379");// .setPassword("123456");//修改看門狗的默認時間30s到60s//config.setLockWatchdogTimeout(60000);// 創建RedissonClient對象return Redisson.create(config);}//這個只針對轉化為string類型@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);//默認是JDK序列化 發現key和value前面都多了一串特殊字符StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();template.setKeySerializer(stringRedisSerializer);template.setValueSerializer(stringRedisSerializer);return template;}
}
三、實現類
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;/*** @Author ZGM* @DateTime 2023/8/15* @description*/
@Service
@Slf4j
public class UserService {@Autowiredprivate RedissonClient redissonClient;@AutowiredRedisTemplate<String, Object> redisTemplate;public void test1() throws InterruptedException {//定義一個keyString key = UUID.randomUUID().toString();// 占鎖 沒有拿到鎖的會自動阻塞// watchDog 機制 : 鎖自動加了默認30秒過期// 如果業務代碼耗時長,鎖也會自動續期RLock lock = redissonClient.getLock(key);//只有lock()和tryLock(5000, TimeUnit.MILLISECONDS)會觸發看門狗機制Boolean isLocked = lock.tryLock(5000, TimeUnit.MILLISECONDS);if(!isLocked) {log.info("獲取鎖失敗");}long a = System.currentTimeMillis();try {Thread.sleep(60000);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}long b = System.currentTimeMillis();System.out.println(b-a);}}
四、測試結果
可以下載redis desktop manager軟件來查看redis里面存放的東西
紅色框內的TTL值就是過期時間,默認-1,表示永不過期,指定過期時間后就變成你指定的值了。
上面的方法,我們讓線程睡眠60S,代表我們的業務執行時間,在調用這個方法時,我們可以在
redis desktop manager軟件上實時查看鎖的過期時間,第一次過期時間為30S,10S后刷新過期時間為30S,也就是說,它每隔10S會檢驗這個鎖是否釋放,沒有的話,會一直給你刷新鎖的持有時間變為30S,直到任務完成。
五、看門狗機制
上面所說的自動刷新鎖的持有時間,就是通過這個看門狗機制來實現的。它默認的鎖的持有時間是30S,每隔30/3也就是10S會刷新鎖的持有時間,我們也可以通過redisson的Config 類的config.setLockWatchdogTimeout(60000)來修改過期時間。實際上鎖的持有時間就是lockWatchdogTimeout的值,只不過默認設置的30S而已,同樣的,鎖的刷新時間也是每隔lockWatchdogTimeout/3秒執行一次,如果設置的時間為60S,就是鎖的過期時間為60S,每隔20S執行一次。
所謂的自動刷新,其實是在獲取鎖的時候,開了一個線程來監控這個鎖,我們先按照默認的30S過期時間來計算,假設當前業務的執行時間在10S之內,在第10S時此線程監控到該鎖已被正常釋放,則不會刷新鎖的過期時間,反之,在第10S時此線程監控到該鎖仍被此線程持有,那也就是業務還未執行完,它就會幫你刷新過期時間。即使redis的服務器宕機了,也不會出現死鎖,因為當它監控到異常時,也不會刷新過期時間,當redis的服務器恢復時,自動就把它刪除了。
六、注意
注意:只有只有lock()和tryLock(waitTime, TimeUnit.MILLISECONDS)會觸發看門狗機制,在這兩個方法里我們都沒有指定releaseTime,它默認值就是-1,然后才能自動觸發看門狗機制,或者我們在調用獲取鎖的方法時直接指定releaseTime為-1,這樣也可以觸發看門狗機制,一定要注意這一點。
lock()會一直嘗試獲取鎖,知道成功
tryLock(long time, TimeUnit unit),同樣會一直嘗試獲取鎖,但它有等待時間,超過這個時間就直接返回false,不在繼續調用,我比較喜歡這個方法。
七、建議
看門狗機制雖然可以自動刷新鎖的過期時間,用起來也非常方便,但并不是說,所有的方法,都應該開啟此機制。因為啟動此機制的同時,意味著會額外開啟一個線程來監控它,那么就會占用CPU內存。少量線程的情況下,這部分內存占用可以忽略,當請求過多時,就占用比略高了,只能增加服務器,也就是增加成本了。
所以我建議是,在獲取資源的方法里,不開啟看門狗機制,比如,一個接口是返回個人信息的,那么我在調用時,由于某種原因導致鎖已經釋放,但業務還未執行完成,那么會報錯,
信息如下:[Request processing failed; nested exception is java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 62e61f35-6cd5-4fc2-849e-78ad649435e4 thread-id: 72] with root cause
,我們直接捕獲此異常,返回系統繁忙即可。
在更新或者保存資源的方法里,是可以開啟看門狗機制的,這樣雖然執行時間會稍微長一些,但最終會完成任務,不至于讓用戶重復操作。