山東農(nóng)業(yè)大學(xué)學(xué)風(fēng)建設(shè)專題網(wǎng)站網(wǎng)頁(yè)模版
一、背景
- 與分布式鎖相對(duì)應(yīng)的是「單機(jī)鎖」,我們?cè)趯懚嗑€程程序時(shí),避免同時(shí)操作一個(gè)共享變量產(chǎn)生數(shù)據(jù)問(wèn)題,通常會(huì)使用一把鎖來(lái)「互斥」,以保證共享變量的正確性,其使用范圍是在「同一個(gè)進(jìn)程」中。
- 單機(jī)環(huán)境下,我們常用 synchronized 或者 Lock 鎖解決多線程并發(fā)訪問(wèn)產(chǎn)生的數(shù)據(jù)安全問(wèn)題,但是如果是在集群環(huán)境,本地鎖就會(huì)失效。
- 為解決分布式場(chǎng)景下的并發(fā)問(wèn)題, 就需要用到分布式鎖。下面介紹下Redis分布式鎖。
二、實(shí)現(xiàn)思路
1. 如何實(shí)現(xiàn)互斥?
為達(dá)到互斥,可以使用SETNX 命令,這個(gè)命令表示Set If Not Exists,即如果 key 不存在,才會(huì)設(shè)置它的值,否則什么也不做。
如:
SETNX lock 1 //加鎖DEL lock //釋放鎖
但是這樣有個(gè)問(wèn)題,當(dāng)客戶端 1 拿到鎖后,如果發(fā)生下面的場(chǎng)景,就會(huì)造成「死鎖」:
①程序處理業(yè)務(wù)邏輯異常,未釋放鎖
②拿到鎖后進(jìn)程掛了,沒(méi)機(jī)會(huì)釋放鎖
這時(shí),這個(gè)客戶端就會(huì)一直占用這個(gè)鎖,而其它客戶端就「永遠(yuǎn)」拿不到這把鎖了。
2. 如何避免死鎖?
鎖無(wú)法釋放,產(chǎn)生「死鎖」。那么我們給這個(gè)鎖加個(gè)「租期」,讓它在一定時(shí)間內(nèi)如果一直沒(méi)釋放就過(guò)期,問(wèn)題不就解決了。Redis支持這種語(yǔ)法,示例:
SETNX lock 1 // 加鎖
EXPIRE lock 10 // 10s后自動(dòng)過(guò)期
但這樣真的沒(méi)問(wèn)題了嗎?
No!
現(xiàn)在的操作,加鎖、設(shè)置過(guò)期是 2 條命令,有沒(méi)有可能只執(zhí)行了第一條,第二條卻「來(lái)不及」執(zhí)行的情況發(fā)生呢?例如:
①SETNX 執(zhí)行成功,執(zhí)行 EXPIRE 時(shí)由于網(wǎng)絡(luò)問(wèn)題,執(zhí)行失敗
②SETNX 執(zhí)行成功,Redis 異常宕機(jī),EXPIRE 沒(méi)有機(jī)會(huì)執(zhí)行
③SETNX 執(zhí)行成功,客戶端異常崩潰,EXPIRE 也沒(méi)有機(jī)會(huì)執(zhí)行
總之,這兩條命令不能保證是原子操作(一起成功),就有潛在的風(fēng)險(xiǎn)導(dǎo)致過(guò)期時(shí)間設(shè)置失敗,依舊發(fā)生「死鎖」問(wèn)題。
如何解決?
Redis 2.6.12 之后,Redis 擴(kuò)展了 SET 命令的參數(shù),用這一條命令就可以執(zhí)行上述兩步操作:
// 一條命令保證原子性執(zhí)行SET lock 1 EX 10 NX
我們?cè)賮?lái)看分析下,它還有什么問(wèn)題?
試想這樣一種場(chǎng)景:
①客戶端 1 加鎖成功,開始操作共享資源
②客戶端 1 操作共享資源的時(shí)間,「超過(guò)」了鎖的過(guò)期時(shí)間,鎖被「自動(dòng)釋放」
③客戶端 2 加鎖成功,開始操作共享資源
④客戶端 1 操作共享資源完成,釋放鎖(但釋放的是客戶端 2 的鎖)
這里存在兩個(gè)嚴(yán)重的問(wèn)題:
①鎖過(guò)期:客戶端 1 操作共享資源耗時(shí)太久,導(dǎo)致鎖被自動(dòng)釋放,之后被客戶端 2 持有
②釋放別人的鎖:客戶端 1 操作共享資源完成后,卻又釋放了客戶端 2 的鎖
3. 鎖被別人釋放怎么辦?
解決辦法是:客戶端在加鎖時(shí),設(shè)置一個(gè)只有自己知道的「唯一標(biāo)識(shí)」進(jìn)去。釋放時(shí),先判斷這把鎖是否是自己所有,是的話再進(jìn)行釋放。
這樣涉及到兩步操作:
①判斷這把鎖是否是自己所有;
②釋放鎖。
非原子性,也會(huì)出現(xiàn)并發(fā)問(wèn)題,如何解決呢?Lua腳本
我們可以寫好Lua腳本后交給Redis執(zhí)行,腳本如下:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
4. 鎖過(guò)期怎么辦?
方案:
- 可能是我們?cè)u(píng)估操作共享資源的時(shí)間不準(zhǔn)確導(dǎo)致的??梢浴溉哂唷惯m量過(guò)期時(shí)間,降低鎖提前過(guò)期的概率。
- 加鎖時(shí),先設(shè)置一個(gè)過(guò)期時(shí)間,然后我們開啟一個(gè)「守護(hù)線程」,定時(shí)去檢測(cè)這個(gè)鎖的失效時(shí)間,如果鎖快要過(guò)期了,操作共享資源還未完成,那么就自動(dòng)對(duì)鎖進(jìn)行「續(xù)期」,重新設(shè)置過(guò)期時(shí)間。
備注:個(gè)人根據(jù)項(xiàng)目的業(yè)務(wù)情況,考慮「鎖過(guò)期」對(duì)業(yè)務(wù)的影響,看是否需要續(xù)期。
5. Redisson
Redisson 是一個(gè) Java 語(yǔ)言實(shí)現(xiàn)的 Redis SDK 客戶端,在使用分布式鎖時(shí),它就采用了「自動(dòng)續(xù)期」的方案來(lái)避免鎖過(guò)期,這個(gè)守護(hù)線程我們一般也把它叫做「看門狗」線程。
除此之外,這個(gè) SDK 還封裝了很多易用的功能:
- 可重入鎖
- 樂(lè)觀鎖
- 公平鎖
- 讀寫鎖
- Redlock
6. Redlock
之前分析的場(chǎng)景都是,鎖在「單個(gè)」Redis 實(shí)例中可能產(chǎn)生的問(wèn)題,并沒(méi)有涉及到 Redis 的部署架構(gòu)細(xì)節(jié)。
而我們?cè)谑褂?Redis 時(shí),一般會(huì)采用主從集群 + 哨兵的模式部署,這樣做的好處在于,當(dāng)主庫(kù)異常宕機(jī)時(shí),哨兵可以實(shí)現(xiàn)「故障自動(dòng)切換」,把從庫(kù)提升為主庫(kù),繼續(xù)提供服務(wù),以此保證可用性。
那當(dāng)「主從發(fā)生切換」時(shí),這個(gè)分布鎖會(huì)依舊安全嗎?
試想這樣的場(chǎng)景:
- 客戶端 1 在主庫(kù)上執(zhí)行 SET 命令,加鎖成功
- 此時(shí),主庫(kù)異常宕機(jī),SET 命令還未同步到從庫(kù)上(主從復(fù)制是異步的)
- 從庫(kù)被哨兵提升為新主庫(kù),這個(gè)鎖在新的主庫(kù)上,丟失了!
為此,Redis 的作者提出一種解決方案,就是我們經(jīng)常聽(tīng)到的 Redlock(紅鎖)。
Redlock 的方案基于 2 個(gè)前提:
不再需要部署從庫(kù)和哨兵實(shí)例,只部署主庫(kù)
但主庫(kù)要部署多個(gè),官方推薦至少 5 個(gè)實(shí)例
也就是說(shuō),想用使用 Redlock,你至少要部署 5 個(gè) Redis 實(shí)例,而且都是主庫(kù),它們之間沒(méi)有任何關(guān)系,都是一個(gè)個(gè)孤立的實(shí)例。
注意:不是部署 Redis Cluster,就是部署 5 個(gè)簡(jiǎn)單的 Redis 實(shí)例。
Redlock流程是這樣的,一共分為 5 步:
- 客戶端先獲取「當(dāng)前時(shí)間戳T1」
- 客戶端依次向這 5 個(gè) Redis 實(shí)例發(fā)起加鎖請(qǐng)求(用前面講到的 SET 命令),且每個(gè)請(qǐng)求會(huì)設(shè)置超時(shí)時(shí)間(毫秒級(jí),要遠(yuǎn)小于鎖的有效時(shí)間),如果某一個(gè)實(shí)例加鎖失敗(包括網(wǎng)絡(luò)超時(shí)、鎖被其它人持有等各種異常情況),就立即向下一個(gè) Redis 實(shí)例申請(qǐng)加鎖
- 如果客戶端從 >=3 個(gè)(大多數(shù))以上 Redis 實(shí)例加鎖成功,則再次獲取「當(dāng)前時(shí)間戳T2」,如果 T2 - T1 < 鎖的過(guò)期時(shí)間,此時(shí),認(rèn)為客戶端加鎖成功,否則認(rèn)為加鎖失敗
- 加鎖成功,去操作共享資源(例如修改 MySQL 某一行,或發(fā)起一個(gè) API 請(qǐng)求)
- 加鎖失敗,向「全部節(jié)點(diǎn)」發(fā)起釋放鎖請(qǐng)求(前面講到的 Lua 腳本釋放鎖)
上述過(guò)程有4個(gè)重點(diǎn):
- 客戶端在多個(gè) Redis 實(shí)例上申請(qǐng)加鎖
- 必須保證大多數(shù)節(jié)點(diǎn)加鎖成功
- 大多數(shù)節(jié)點(diǎn)加鎖的總耗時(shí),要小于鎖設(shè)置的過(guò)期時(shí)間
- 釋放鎖,要向全部節(jié)點(diǎn)發(fā)起釋放鎖請(qǐng)求
實(shí)際生產(chǎn)中,Redlock很少使用,所以就簡(jiǎn)單介紹到這里。
三、springboot項(xiàng)目實(shí)現(xiàn)Redis分布式鎖
1. 依賴
<!--Springboot依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.3.12.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --><!--Springboot 測(cè)試依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.3.12.RELEASE</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --><!--Springboot Redis依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.3.12.RELEASE</version><!--排除掉默認(rèn)的 lettuce ,lettuce 在使用中存在偶爾連接超時(shí)問(wèn)題--><exclusions><exclusion><artifactId>lettuce-core</artifactId><groupId>io.lettuce</groupId></exclusion></exclusions></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-commons --><!-- Redis 需要引入這個(gè)依賴,否則報(bào)錯(cuò) --><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId><version>2.3.9.RELEASE</version></dependency>
2. Redis配置類
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;import java.util.HashSet;
import java.util.Set;@Configuration
public class RedisConfig {@Value("${spring.redis.cluster.nodes:IP:Port}")private String clusterNodes;@Value("${spring.redis.cluster.max-redirects:3}")private int maxRedirects;@Value("${spring.redis.password:***}")private String password;@Value("${spring.redis.timeout:3000}")private int timeout;/*** 最大空閑數(shù)*/@Value("${spring.redis.maxIdle:100}")private int maxIdle;/*** 控制一個(gè)pool可分配多少個(gè)jedis實(shí)例*/@Value("${spring.redis.maxTotal:100}")private int maxTotal;/*** 最大建立連接等待時(shí)間。如果超過(guò)此時(shí)間將接到異常。設(shè)為-1表示無(wú)限制*/@Value("${spring.redis.maxWaitMillis:5000}")private int maxWaitMillis;/*** 最小空閑數(shù)*/@Value("${spring.redis.minIdle:5}")private int minIdle;/*** 連接的最小空閑時(shí)間 默認(rèn)1800000毫秒(30分鐘)*/@Value("${spring.redis.minEvictableIdleTimeMillis:300000}")private int minEvictableIdleTimeMillis;/*** 每次釋放連接的最大數(shù)目,默認(rèn)3*/@Value("${spring.redis.numTestsPerEvictionRun:3}")private int numTestsPerEvictionRun;/*** 逐出掃描的時(shí)間間隔(毫秒) 如果為負(fù)數(shù),則不運(yùn)行逐出線程, 默認(rèn)-1*/@Value("${spring.redis.timeBetweenEvictionRunsMillis:30000}")private int timeBetweenEvictionRunsMillis;/*** 是否在從池中取出連接前進(jìn)行檢驗(yàn),如果檢驗(yàn)失敗,則從池中去除連接并嘗試取出另一個(gè)*/@Value("${spring.redis.testOnBorrow:true}")private boolean testOnBorrow;/*** 在空閑時(shí)檢查有效性, 默認(rèn)false*/@Value("${spring.redis.testWhileIdle:true}")private boolean testWhileIdle;@Beanpublic JedisPoolConfig getJedisPoolConfig() {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 最大空閑數(shù)jedisPoolConfig.setMaxIdle(maxIdle);// 最小空閑數(shù)jedisPoolConfig.setMinIdle(minIdle);// 連接池的最大數(shù)據(jù)庫(kù)連接數(shù)jedisPoolConfig.setMaxTotal(maxTotal);// 最大建立連接等待時(shí)間jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);// 逐出連接的最小空閑時(shí)間 默認(rèn)1800000毫秒(30分鐘)jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);// 每次逐出檢查時(shí) 逐出的最大數(shù)目 如果為負(fù)數(shù)就是 : 1/abs(n), 默認(rèn)3jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);// 逐出掃描的時(shí)間間隔(毫秒) 如果為負(fù)數(shù),則不運(yùn)行逐出線程, 默認(rèn)-1jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);// 是否在從池中取出連接前進(jìn)行檢驗(yàn),如果檢驗(yàn)失敗,則從池中去除連接并嘗試取出另一個(gè)jedisPoolConfig.setTestOnBorrow(testOnBorrow);// 在空閑時(shí)檢查有效性, 默認(rèn)falsejedisPoolConfig.setTestWhileIdle(testWhileIdle);return jedisPoolConfig;}/*** Redis集群的配置** @return RedisClusterConfiguration*/@Beanpublic RedisClusterConfiguration redisClusterConfiguration() {RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();// Set<RedisNode> clusterNodesString[] serverArray = clusterNodes.split(",");Set<RedisNode> nodes = new HashSet<>();for (String ipPort : serverArray) {String[] ipAndPort = ipPort.split(":");nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.parseInt(ipAndPort[1])));}redisClusterConfiguration.setClusterNodes(nodes);redisClusterConfiguration.setMaxRedirects(maxRedirects);redisClusterConfiguration.setPassword(RedisPassword.of(password));return redisClusterConfiguration;}/*** redis連接工廠類** @return JedisConnectionFactory*/@Beanpublic JedisConnectionFactory jedisConnectionFactory() {// 集群模式return new JedisConnectionFactory(redisClusterConfiguration(), getJedisPoolConfig());}@Beanpublic RedisTemplate<String, String> poolRedisTemplate() {RedisTemplate<String, String> template = new RedisTemplate<>();template.setConnectionFactory(jedisConnectionFactory());// 如果不配置Serializer,那么存儲(chǔ)的時(shí)候缺省使用String,如果用User類型存儲(chǔ),那么會(huì)提示錯(cuò)誤User can't cast to String!StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(mapper);template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(new StringRedisSerializer());template.setDefaultSerializer(jackson2JsonRedisSerializer);template.setEnableDefaultSerializer(true);template.afterPropertiesSet();return template;}
}
3. Redis 分布式鎖
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@Service
public class RedisService {@Autowired@Qualifier("poolRedisTemplate")private RedisTemplate<String, String> stringRedisTemplate;public String getStr(String key) {return stringRedisTemplate.opsForValue().get(key);}/*** key 不存在 則進(jìn)行設(shè)置* <p>* 原子性操作** @param k 鍵* @param v 值* @param timeout 超時(shí)時(shí)間* @param unit 超時(shí)時(shí)間的單位* @return key存在返回false,設(shè)置失敗*/public Boolean setIfAbsent(String k, String v, long timeout, TimeUnit unit) {return stringRedisTemplate.opsForValue().setIfAbsent(k, v, timeout, unit);}/*** 加分布式鎖** @param key 鎖* @param timeout 超時(shí)時(shí)間,單位:秒* @return 空串 表示加鎖失敗, uuid 表示加鎖成功,后續(xù)uuid要作為解鎖的標(biāo)識(shí)*/public String tryLock(String key, long timeout) {//釋放鎖時(shí)要根據(jù)uuid判斷是否是自己的鎖,防止釋放其他人的鎖
// String uuid = System.currentTimeMillis() + "";String uuid = UUID.randomUUID().toString();Boolean tryLock = setIfAbsent(key, uuid, timeout, TimeUnit.SECONDS);if (tryLock) {return uuid;}//加鎖失敗,返回 空串return "";}/*** “判斷值與舊值是否相等,相等則刪除鍵” 的 Lua 腳本,保證原子性操作*/private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";/*** 釋放分布式鎖** @param key 鎖* @param oldValue 加鎖時(shí)放入的標(biāo)識(shí)* @return 返回釋放鎖成功失敗*/public Boolean unLock(String key, String oldValue) {List<String> keys = new ArrayList<>();keys.add(key);List<String> args = new ArrayList<>();//這里需要加下引號(hào),原因:stringRedisTemplate獲取的值帶引號(hào)(即redis.call('get', KEYS[1]) 的結(jié)果帶引號(hào)),而ARGV[1]不帶引號(hào),比較時(shí)會(huì)出現(xiàn)不等的問(wèn)題args.add("\"" + oldValue + "\"");Long result = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {Object nativeConnection = connection.getNativeConnection();// 集群模式和單機(jī)模式雖然執(zhí)行腳本的方法一樣,但是沒(méi)有共同的接口,所以只能分開執(zhí)行// 集群模式if (nativeConnection instanceof JedisCluster) {return (Long) ((JedisCluster) nativeConnection).eval(SCRIPT, keys, args);}// 單機(jī)模式else if (nativeConnection instanceof Jedis) {return (Long) ((Jedis) nativeConnection).eval(SCRIPT, keys, args);}return 0L;});return result == 1L;}}
4. 測(cè)試類
import com.example.demo.redis.RedisService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StringUtils;@SpringBootTest
public class RedisTest {@Autowiredprivate RedisService redisService;@Testpublic void test() {String key = "test-lock-1235637";String uid = redisService.tryLock(key, 10000);if(StringUtils.isEmpty(uid)){return;}System.out.println(uid);String uid2 = redisService.tryLock("test-lock-123", 3000);System.out.println("重新獲得鎖是否成功:" + !StringUtils.isEmpty(uid2));try {//執(zhí)行業(yè)務(wù)代碼System.out.println("111111111");} finally {Boolean unlockSuccess = redisService.unLock(key, uid);System.out.println("解鎖結(jié)果:" + unlockSuccess);System.out.println(redisService.getStr(key));}}
}
四、springboot項(xiàng)目中Redission的簡(jiǎn)單使用
1. 依賴
<!--redission相關(guān)依賴--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.0</version></dependency>
2. 配置類
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;@Configuration
public class RedissionConfig {@Beanpublic RedissonClient getRedisson() {
// //單機(jī)
// Config config = new Config();
// //單機(jī)模式 依次設(shè)置redis地址和密碼
// config.useSingleServer().
// setAddress("redis://" + host + ":" + port);
// RedissonClient redisson = Redisson.create(config);
//
// //主從
// Config config = new Config();
// config.useMasterSlaveServers()
// .setMasterAddress("redis://127.0.0.1:6379")
// .addSlaveAddress("redis://127.0.0.1:6389", "127.0.0.1:6332", "127.0.0.1:6419")
// .addSlaveAddress("redis://127.0.0.1:6399");
// RedissonClient redisson = Redisson.create(config);
//
//
// //哨兵
// Config config = new Config();
// config.useSentinelServers()
// .setMasterName("mymaster")
// .addSentinelAddress("redis://127.0.0.1:26389", "127.0.0.1:26379")
// .addSentinelAddress("redis://127.0.0.1:26319");
// RedissonClient redisson = Redisson.create(config);//集群 ,,,,,Config config = new Config();config.useClusterServers().setScanInterval(2000) // cluster state scan interval in milliseconds.addNodeAddress("redis://ip:port", "redis://ip:port").setPassword("***");RedissonClient redisson = Redisson.create(config);return redisson;}
}
3. 測(cè)試類
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;@SpringBootTest
public class RedissionTest {@Resourceprivate RedissonClient redisson;@Testpublic void testRedission() {System.out.println("開始執(zhí)行");String lockKey = "123456";RLock lock = redisson.getLock(lockKey);System.out.println("獲取鎖");try {//嘗試獲取鎖,參數(shù)分別是:獲取鎖的最大等待時(shí)間(期間會(huì)重試),鎖自動(dòng)釋放時(shí)間,時(shí)間單位boolean b = lock.tryLock(1, 10, TimeUnit.SECONDS);System.out.println("是否獲取鎖:" + b);//執(zhí)行業(yè)務(wù)邏輯System.out.println("執(zhí)行業(yè)務(wù)邏輯....");Thread.sleep(3000);} catch (Exception e) {System.out.println("系統(tǒng)異常,稍后重試....");} finally {//刪除鎖lock.unlock();System.out.println("解鎖成功");}}
}
五、參考文獻(xiàn)
https://mp.weixin.qq.com/s/2et43aJT6qjBsJ8Z9pcZcQ