SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的方法示例
上篇 《SpringBoot 集成 redis 分布式鎖優(yōu)化》對(duì)死鎖的問(wèn)題進(jìn)行了優(yōu)化,今天介紹的是 redis 官方推薦使用的 Redisson ,Redisson 架設(shè)在 redis 基礎(chǔ)上的 Java 駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 鍵值數(shù)據(jù)庫(kù)。功能非常強(qiáng)大,解決了很多分布式架構(gòu)中的問(wèn)題。
Github的wiki地址: https://github.com/redisson/redisson/wiki
官方文檔: https://github.com/redisson/redisson/wiki/目錄
項(xiàng)目代碼結(jié)構(gòu)圖:
導(dǎo)入依賴
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.8.0</version> </dependency>
屬性配置
在 application.properites 資源文件中添加單機(jī)&哨兵相關(guān)配置
server.port=3000 # redisson lock 單機(jī)模式 redisson.address=redis://127.0.0.1:6379 redisson.password= #哨兵模式 #redisson.master-name= master #redisson.password= #redisson.sentinel-addresses=10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381
注意:
這里如果不加 redis:// 前綴會(huì)報(bào) URI 構(gòu)建錯(cuò)誤
Caused by: java.net.URISyntaxException: Illegal character in scheme name at index 0
更多的配置信息可以去官網(wǎng)查看
定義Lock的接口定義類
package com.tuhu.thirdsample.service; import org.redisson.api.RLock; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 10:48 */ public interface DistributedLocker { RLock lock(String lockKey); RLock lock(String lockKey, int timeout); RLock lock(String lockKey, TimeUnit unit, int timeout); boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime); void unlock(String lockKey); void unlock(RLock lock); }
Lock接口實(shí)現(xiàn)類
package com.tuhu.thirdsample.service; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 10:49 */ public class RedissonDistributedLocker implements DistributedLocker{ private RedissonClient redissonClient; @Override public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } @Override public RLock lock(String lockKey, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); lock.lock(leaseTime, TimeUnit.SECONDS); return lock; } @Override public RLock lock(String lockKey, TimeUnit unit ,int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } @Override public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } } @Override public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } @Override public void unlock(RLock lock) { lock.unlock(); } public void setRedissonClient(RedissonClient redissonClient) { this.redissonClient = redissonClient; } }
redisson屬性裝配類
package com.tuhu.thirdsample.common; import lombok.Data; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * @author chendesheng * @create 2019/10/11 20:04 */ @Configuration @ConfigurationProperties(prefix = "redisson") @ConditionalOnProperty("redisson.password") @Data public class RedissonProperties { private int timeout = 3000; private String address; private String password; private int database = 0; private int connectionPoolSize = 64; private int connectionMinimumIdleSize=10; private int slaveConnectionPoolSize = 250; private int masterConnectionPoolSize = 250; private String[] sentinelAddresses; private String masterName; }
SpringBoot自動(dòng)裝配類
package com.tuhu.thirdsample.configuration; import com.tuhu.thirdsample.common.RedissonProperties; import com.tuhu.thirdsample.service.DistributedLocker; import com.tuhu.thirdsample.service.RedissonDistributedLocker; import com.tuhu.thirdsample.util.RedissonLockUtil; import org.apache.commons.lang3.StringUtils; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SentinelServersConfig; import org.redisson.config.SingleServerConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author chendesheng * @create 2019/10/12 10:50 */ @Configuration @ConditionalOnClass(Config.class) @EnableConfigurationProperties(RedissonProperties.class) public class RedissonAutoConfiguration { @Autowired private RedissonProperties redissonProperties; /** * 哨兵模式自動(dòng)裝配 * @return */ @Bean @ConditionalOnProperty(name="redisson.master-name") RedissonClient redissonSentinel() { Config config = new Config(); SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redissonProperties.getSentinelAddresses()) .setMasterName(redissonProperties.getMasterName()) .setTimeout(redissonProperties.getTimeout()) .setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize()) .setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize()); if(StringUtils.isNotBlank(redissonProperties.getPassword())) { serverConfig.setPassword(redissonProperties.getPassword()); } return Redisson.create(config); } /** * 單機(jī)模式自動(dòng)裝配 * @return */ @Bean @ConditionalOnProperty(name="redisson.address") RedissonClient redissonSingle() { Config config = new Config(); SingleServerConfig serverConfig = config.useSingleServer() .setAddress(redissonProperties.getAddress()) .setTimeout(redissonProperties.getTimeout()) .setConnectionPoolSize(redissonProperties.getConnectionPoolSize()) .setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize()); if(StringUtils.isNotBlank(redissonProperties.getPassword())) { serverConfig.setPassword(redissonProperties.getPassword()); } return Redisson.create(config); } /** * 裝配locker類,并將實(shí)例注入到RedissLockUtil中 * @return */ @Bean DistributedLocker distributedLocker(RedissonClient redissonClient) { DistributedLocker locker = new RedissonDistributedLocker(); ((RedissonDistributedLocker) locker).setRedissonClient(redissonClient); RedissonLockUtil.setLocker(locker); return locker; } }
Lock幫助類
package com.tuhu.thirdsample.util; import com.tuhu.thirdsample.service.DistributedLocker; import org.redisson.api.RLock; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 10:54 */ public class RedissonLockUtil { private static DistributedLocker redissLock; public static void setLocker(DistributedLocker locker) { redissLock = locker; } /** * 加鎖 * @param lockKey * @return */ public static RLock lock(String lockKey) { return redissLock.lock(lockKey); } /** * 釋放鎖 * @param lockKey */ public static void unlock(String lockKey) { redissLock.unlock(lockKey); } /** * 釋放鎖 * @param lock */ public static void unlock(RLock lock) { redissLock.unlock(lock); } /** * 帶超時(shí)的鎖 * @param lockKey * @param timeout 超時(shí)時(shí)間 單位:秒 */ public static RLock lock(String lockKey, int timeout) { return redissLock.lock(lockKey, timeout); } /** * 帶超時(shí)的鎖 * @param lockKey * @param unit 時(shí)間單位 * @param timeout 超時(shí)時(shí)間 */ public static RLock lock(String lockKey, TimeUnit unit , int timeout) { return redissLock.lock(lockKey, unit, timeout); } /** * 嘗試獲取鎖 * @param lockKey * @param waitTime 最多等待時(shí)間 * @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間 * @return */ public static boolean tryLock(String lockKey, int waitTime, int leaseTime) { return redissLock.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime); } /** * 嘗試獲取鎖 * @param lockKey * @param unit 時(shí)間單位 * @param waitTime 最多等待時(shí)間 * @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間 * @return */ public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { return redissLock.tryLock(lockKey, unit, waitTime, leaseTime); } }
控制層
package com.tuhu.thirdsample.task; import com.tuhu.thirdsample.common.KeyConst; 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.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 11:03 */ @RestController @RequestMapping("/lock") @Slf4j public class LockController { @Autowired RedissonClient redissonClient; @GetMapping("/task") public void task(){ log.info("task start"); RLock lock = redissonClient.getLock(KeyConst.REDIS_LOCK_KEY); boolean getLock = false; try { if (getLock = lock.tryLock(0,5,TimeUnit.SECONDS)){ //執(zhí)行業(yè)務(wù)邏輯 System.out.println("拿到鎖干活"); }else { log.info("Redisson分布式鎖沒(méi)有獲得鎖:{},ThreadName:{}",KeyConst.REDIS_LOCK_KEY,Thread.currentThread().getName()); } } catch (InterruptedException e) { log.error("Redisson 獲取分布式鎖異常,異常信息:{}",e); }finally { if (!getLock){ return; } //如果演示的話需要注釋該代碼;實(shí)際應(yīng)該放開(kāi) //lock.unlock(); //log.info("Redisson分布式鎖釋放鎖:{},ThreadName :{}", KeyConst.REDIS_LOCK_KEY, Thread.currentThread().getName()); } } }
RLock 繼承自 java.util.concurrent.locks.Lock
,可以將其理解為一個(gè)重入鎖,需要手動(dòng)加鎖和釋放鎖 。
來(lái)看它其中的一個(gè)方法:tryLock(long waitTime, long leaseTime, TimeUnit unit)
getLock = lock.tryLock(0,5,TimeUnit.SECONDS)
通過(guò) tryLock() 的參數(shù)可以看出,在獲取該鎖時(shí)如果被其他線程先拿到鎖就會(huì)進(jìn)入等待,等待 waitTime 時(shí)間,如果還沒(méi)用機(jī)會(huì)獲取到鎖就放棄,返回 false;若獲得了鎖,除非是調(diào)用 unlock 釋放,那么會(huì)一直持有鎖,直到超過(guò) leaseTime 指定的時(shí)間。
以上就是 Redisson 實(shí)現(xiàn)分布式鎖的核心方法,有人可能要問(wèn),那怎么確定拿的是同一把鎖,分布式鎖在哪?
這就是 Redisson 的強(qiáng)大之處,其底層還是使用的 Redis 來(lái)作分布式鎖,在我們的RedissonManager中已經(jīng)指定了 Redis 實(shí)例,Redisson 會(huì)進(jìn)行托管,其原理與我們手動(dòng)實(shí)現(xiàn) Redis 分布式鎖類似。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)文件讀取和寫(xiě)入過(guò)程解析
這篇文章主要介紹了Java實(shí)現(xiàn)文件讀取和寫(xiě)入過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值。,需要的朋友可以參考下2019-10-10Java怎么獲取當(dāng)前時(shí)間、計(jì)算程序運(yùn)行時(shí)間源碼詳解(超詳細(xì)!)
有的時(shí)候,我們需要查看某一段代碼的性能如何,最為簡(jiǎn)單的方式,可以通過(guò)計(jì)算該段代碼執(zhí)行的耗時(shí),來(lái)進(jìn)行簡(jiǎn)單的判斷,這篇文章主要給大家介紹了關(guān)于Java怎么獲取當(dāng)前時(shí)間、計(jì)算程序運(yùn)行時(shí)間的相關(guān)資料,需要的朋友可以參考下2024-07-07Mybatis如何實(shí)現(xiàn)關(guān)聯(lián)屬性懶加載
這篇文章主要介紹了Mybatis如何實(shí)現(xiàn)關(guān)聯(lián)屬性懶加載的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Spring Cloud 的 Hystrix.功能及實(shí)踐詳解
這篇文章主要介紹了Spring Cloud 的 Hystrix.功能及實(shí)踐詳解,Hystrix 具備服務(wù)降級(jí)、服務(wù)熔斷、線程和信號(hào)隔離、請(qǐng)求緩存、請(qǐng)求合并以及服務(wù)監(jiān)控等強(qiáng)大功能,需要的朋友可以參考下2019-07-07Spring?Boot緩存實(shí)戰(zhàn)之Redis?設(shè)置有效時(shí)間和自動(dòng)刷新緩存功能(時(shí)間支持在配置文件中配置)
這篇文章主要介紹了Spring?Boot緩存實(shí)戰(zhàn)?Redis?設(shè)置有效時(shí)間和自動(dòng)刷新緩存,時(shí)間支持在配置文件中配置,需要的朋友可以參考下2023-05-05Java實(shí)現(xiàn)PDF轉(zhuǎn)圖片的三種方法
有些時(shí)候我們需要在項(xiàng)目中展示PDF,所以我們可以將PDF轉(zhuǎn)為圖片,然后已圖片的方式展示,效果很好,Java使用各種技術(shù)將pdf轉(zhuǎn)換成圖片格式,并且內(nèi)容不失幀,本文給大家介紹了三種方法實(shí)現(xiàn)PDF轉(zhuǎn)圖片的案例,需要的朋友可以參考下2023-10-10Spring Boot項(xiàng)目打包指定包名實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Spring Boot項(xiàng)目打包指定包名實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11