SpringBoot 整合 Redisson 實現分散式鎖
上篇 《SpringBoot 整合 redis 分散式鎖優化》對死鎖的問題進行了優化,今天介紹的是 redis 官方推薦使用的 Redisson ,Redisson 架設在 redis 基礎上的 Java 駐記憶體資料網格(In-Memory Data Grid),基於NIO的 Netty 框架上,利用了 redis 鍵值資料庫。功能非常強大,解決了很多分散式架構中的問題。
Github的wiki地址:github.com/redisson/re…
專案程式碼結構圖:
匯入依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.0</version>
</dependency>
複製程式碼
屬性配置
在application.properites資原始檔中新增單機&哨兵相關配置
server.port=3000
# redisson lock 單機模式
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:// 字首會報 URI 構建錯誤
Caused by: java.net.URISyntaxException: Illegal character in scheme name at index 0
複製程式碼
更多的配置資訊可以去官網檢視
定義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,int waitTime,int leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
}
複製程式碼
Lock介面實現類
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,int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout,unit);
return lock;
}
@Override
public boolean tryLock(String lockKey,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自動裝配類
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;
/**
* 哨兵模式自動裝配
* @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);
}
/**
* 單機模式自動裝配
* @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類,並將例項注入到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);
}
/**
* 帶超時的鎖
* @param lockKey
* @param timeout 超時時間 單位:秒
*/
public static RLock lock(String lockKey,int timeout) {
return redissLock.lock(lockKey,timeout);
}
/**
* 帶超時的鎖
* @param lockKey
* @param unit 時間單位
* @param timeout 超時時間
*/
public static RLock lock(String lockKey,unit,timeout);
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param waitTime 最多等待時間
* @param leaseTime 上鎖後自動釋放鎖時間
* @return
*/
public static boolean tryLock(String lockKey,int leaseTime) {
return redissLock.tryLock(lockKey,TimeUnit.SECONDS,waitTime,leaseTime);
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param unit 時間單位
* @param waitTime 最多等待時間
* @param leaseTime 上鎖後自動釋放鎖時間
* @return
*/
public static boolean tryLock(String lockKey,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)){
//執行業務邏輯
System.out.println("拿到鎖幹活");
}else {
log.info("Redisson分散式鎖沒有獲得鎖:{},ThreadName:{}",KeyConst.REDIS_LOCK_KEY,Thread.currentThread().getName());
}
} catch (InterruptedException e) {
log.error("Redisson 獲取分散式鎖異常,異常資訊:{}",e);
}finally {
if (!getLock){
return;
}
//如果演示的話需要註釋該程式碼;實際應該放開
//lock.unlock();
//log.info("Redisson分散式鎖釋放鎖:{},ThreadName :{}",Thread.currentThread().getName());
}
}
}
複製程式碼
RLock 繼承自 java.util.concurrent.locks.Lock
,可以將其理解為一個重入鎖,需要手動加鎖和釋放鎖 。
來看它其中的一個方法:tryLock(long waitTime,long leaseTime,TimeUnit unit)
getLock = lock.tryLock(0,TimeUnit.SECONDS)
複製程式碼
通過 tryLock() 的引數可以看出,在獲取該鎖時如果被其他執行緒先拿到鎖就會進入等待,等待 waitTime 時間,如果還沒用機會獲取到鎖就放棄,返回 false;若獲得了鎖,除非是呼叫 unlock 釋放,那麼會一直持有鎖,直到超過 leaseTime 指定的時間。
以上就是 Redisson 實現分散式鎖的核心方法,有人可能要問,那怎麼確定拿的是同一把鎖,分散式鎖在哪?
這就是 Redisson 的強大之處,其底層還是使用的 Redis 來作分散式鎖,在我們的RedissonManager中已經指定了 Redis 例項,Redisson 會進行託管,其原理與我們手動實現 Redis 分散式鎖類似。
測試
啟動專案,在postman中輸入url:http://localhost:3000/lock/task
第一次輸入結果,我們可以在控制檯看到:
在鎖過期時間內再次請求,我們可以在控制檯看到:
最後
ok, SpringBoot 整合 Redisson實現分散式鎖已經寫到最後。使用redisson更加體現一切皆物件,我們不需要知道內部如何實現,只需知道如何使用就行。 當然僅僅是知道如何使用是遠遠不夠的,熟悉理解底層原理更為重要!