spring-data-redis自定義實(shí)現(xiàn)看門狗機(jī)制
前言
項目中使用redis分布式鎖解決了點(diǎn)贊和樓層排序得問題,所以這里就對這個redis得分布式鎖進(jìn)行了學(xué)習(xí),一般使用得是redission提供得分布式鎖解決得這個問題,但是知其然更要知其所以然,所以自己就去找了一些資料以及也實(shí)踐了一下就此記錄分享一下。
redission分布式鎖看門狗機(jī)制簡單流程圖

spring-data-redis實(shí)現(xiàn)看門狗機(jī)制指南開始
引入依賴
<!--redis的依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--json工具包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
配置redis連接以及基礎(chǔ)配置
spring:
redis:
host: localhost
port: 6379
lettuce:
timeout: 200000
database: 1
在Spring Boot的配置類中創(chuàng)建一個RedisTemplate的Bean:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 我們?yōu)榱俗约洪_發(fā)方便,一般直接使用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
實(shí)現(xiàn)redis分布式鎖工具類
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisLockUtils {
@Resource
private RedisTemplate redisTemplate;
private volatile static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();
private static final Long SUCCESS = 1L;
public static class LockInfo {
private String key;
private String value;
private int expireTime;
//更新時間
private long renewalTime;
//更新間隔
private long renewalInterval;
public static LockInfo getLockInfo(String key, String value, int expireTime) {
LockInfo lockInfo = new LockInfo();
lockInfo.setKey(key);
lockInfo.setValue(value);
lockInfo.setExpireTime(expireTime);
lockInfo.setRenewalTime(System.currentTimeMillis());
lockInfo.setRenewalInterval(expireTime*1000 *2 / 3);
return lockInfo;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public int getExpireTime() {
return expireTime;
}
public void setExpireTime(int expireTime) {
this.expireTime = expireTime;
}
public long getRenewalTime() {
return renewalTime;
}
public void setRenewalTime(long renewalTime) {
this.renewalTime = renewalTime;
}
public long getRenewalInterval() {
return renewalInterval;
}
public void setRenewalInterval(long renewalInterval) {
this.renewalInterval = renewalInterval;
}
}
/**
* 使用lua腳本更新redis鎖的過期時間
* @param lockKey
* @param value
* @return 成功返回true, 失敗返回false
*/
public boolean renewal(String lockKey, String value, int expireTime) {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Boolean.class);
redisScript.setScriptText(luaScript);
List<String> keys = new ArrayList<>();
keys.add(lockKey);
Object result = redisTemplate.execute(redisScript, keys, value, expireTime);
log.info("更新redis鎖的過期時間:{}", result);
return (boolean) result;
}
/**
* @param lockKey 鎖
* @param value 身份標(biāo)識(保證鎖不會被其他人釋放)
* @param expireTime 鎖的過期時間(單位:秒)
* @return 成功返回true, 失敗返回false
*/
public boolean lock(String lockKey, String value, int expireTime) {
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
if(aBoolean){
lockInfoMap.put(String.valueOf(Thread.currentThread().getId()),LockInfo.getLockInfo(lockKey,value,expireTime));
}
return aBoolean;
}
/**
* redisTemplate解鎖
* @param key
* @param value
* @return 成功返回true, 失敗返回false
*/
public boolean unlock2(String key, String value) {
Object currentValue = redisTemplate.opsForValue().get(key);
boolean result = false;
if (StringUtils.isNotEmpty(String.valueOf(currentValue)) && currentValue.equals(value)) {
lockInfoMap.remove(String.valueOf(Thread.currentThread().getId()));
result = redisTemplate.opsForValue().getOperations().delete(key);
}
return result;
}
/**
* 定時去檢查redis鎖的過期時間
*/
@Scheduled(fixedRate = 1000)
@Async("redisExecutor")
public void renewal() {
long now = System.currentTimeMillis();
for (Map.Entry<String, LockInfo> lockInfoEntry : lockInfoMap.entrySet()) {
LockInfo lockInfo = lockInfoEntry.getValue();
System.out.println("++"+lockInfo.key);
if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) {
renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime());
lockInfo.setRenewalTime(now);
log.info("lockInfo {}", JSON.toJSONString(lockInfo));
}
}
}
/**
* 分布式鎖設(shè)置單獨(dú)線程池
* @return
*/
@Bean("redisExecutor")
public ThreadPoolTaskExecutor redisExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(1);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("redis-renewal-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
executor.initialize();
return executor;
}
}
這個類是一個用于實(shí)現(xiàn)分布式鎖的工具類,主要提供了以下功能:
renewal()方法:定時檢查并更新 Redis 鎖的過期時間。該方法使用@Scheduled注解進(jìn)行定時執(zhí)行,通過遍歷lockInfoMap中保存的鎖信息,判斷是否需要更新鎖的過期時間,并調(diào)用renewal()方法進(jìn)行更新。renewal(String lockKey, String value, int expireTime)方法:使用 Lua 腳本更新 Redis 鎖的過期時間。該方法首先定義了一個 Lua 腳本,然后使用redisTemplate.execute()方法執(zhí)行該腳本,并傳入相應(yīng)的參數(shù)。如果執(zhí)行成功,則返回 true,否則返回 false。lock(String lockKey, String value, int expireTime)方法:獲取分布式鎖。該方法使用 Redis 的setIfAbsent()方法嘗試將鎖的鍵值對存儲到 Redis 中,并設(shè)置相應(yīng)的過期時間。如果存儲成功,則返回 true,表示獲取鎖成功;否則返回 false,表示獲取鎖失敗。unlock2(String key, String value)方法:釋放分布式鎖。該方法首先獲取 Redis 中當(dāng)前的鎖值,然后判斷鎖值是否和傳入的 value 相等。如果相等,則從lockInfoMap中移除鎖信息,并調(diào)用 Redis 的delete()方法刪除鎖的鍵值對。最后返回刪除結(jié)果,表示是否成功釋放鎖。redisExecutor()方法:配置一個單獨(dú)的線程池用于執(zhí)行renewal()方法。該方法創(chuàng)建一個ThreadPoolTaskExecutor對象,并設(shè)置相關(guān)的屬性,如核心線程數(shù)、最大線程數(shù)、隊列容量等。
直接失敗和鎖重試機(jī)制實(shí)現(xiàn)
直接失敗的方式,就是調(diào)用獲取鎖的方法判斷是否加鎖成功,失敗則直接中斷方法執(zhí)行返回
@GetMapping("/test2")
public Object test2(){
boolean a = redisLockUtils.lock("A", "111", 5);
if(!a){
return "獲取鎖失敗";
}
try{
System.out.println("執(zhí)行開始-------------------test2");
TimeUnit.SECONDS.sleep(12);
System.out.println("執(zhí)行結(jié)束-------------------test2");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
redisLockUtils.unlock2("A","111");
System.out.println("釋放鎖-----------------------test2");
}
return null;
}
鎖重試,這里是封裝了獲取鎖的方法,加入了一個重試次數(shù)的限制,通過使用while循環(huán)去嘗試獲取鎖。
private Boolean getLock(int tryNum, String key, String value, int exp) throws InterruptedException {
int i = 0; // 初始化計數(shù)器,記錄嘗試獲取鎖的次數(shù)
boolean flag = false; // 初始化標(biāo)志變量,表示獲取鎖的結(jié)果
while (true) { // 循環(huán)進(jìn)行嘗試獲取鎖的操作
if (i == tryNum) { // 判斷是否達(dá)到嘗試獲取鎖的最大次數(shù)
return flag; // 返回當(dāng)前的獲取鎖結(jié)果
}
flag = redisLockUtils.lock(key, value, exp); // 嘗試獲取鎖,返回是否成功獲取到鎖的結(jié)果
if (flag) { // 如果成功獲取到鎖
return flag; // 直接返回獲取鎖結(jié)果為 true
} else { // 如果未能成功獲取到鎖
i++; // 計數(shù)器加一,表示已經(jīng)嘗試了一次獲取鎖的操作
TimeUnit.SECONDS.sleep(1); // 暫停一秒鐘,等待一段時間后再進(jìn)行下一次獲取鎖的嘗試
}
}
}
效果圖展示


這里設(shè)置的鎖過期是5秒每隔2/3的時間也就是4秒進(jìn)行一次續(xù)期一共續(xù)了3次,因為我中間讓線程睡了12秒??梢钥吹芥i被正常續(xù)費(fèi)了,確保了業(yè)務(wù)的正常執(zhí)行不會搶占資源。
到此這篇關(guān)于spring-data-redis自定義實(shí)現(xiàn)看門狗機(jī)制的文章就介紹到這了,更多相關(guān)spring-data-redis 看門狗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何正確控制springboot中bean的加載順序小結(jié)篇
這篇文章主要介紹了如何正確控制springboot中bean的加載順序總結(jié),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
java中SynchronizedList和Vector的區(qū)別詳解
這篇文章主要介紹了java中SynchronizedList和Vector的區(qū)別詳解,Vector是java.util包中的一個類。 SynchronizedList是java.util.Collections中的一個靜態(tài)內(nèi)部類。,需要的朋友可以參考下2019-06-06
解決spring-boot 打成jar包后 啟動時指定參數(shù)無效的問題
這篇文章主要介紹了解決spring-boot 打成jar包后 啟動時指定參數(shù)無效的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
MyBatis中 #{} 和 ${} 的區(qū)別小結(jié)
MyBatis中#{}和${}是兩種占位符,本文就來介紹一下MyBatis中 #{} 和 ${} 的區(qū)別小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
java 查找list中重復(fù)數(shù)據(jù)實(shí)例詳解
這篇文章主要介紹了java 查找list中重復(fù)數(shù)據(jù)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-01-01

