前言
該篇是基于springboot 項目整合 Redisson 實作對redis的操作。
内容:
1.以自定注解aop方式實作對接口使用分布式鎖
2.使用RedissonClient對一些集合的正常操作,資料查詢,存儲等
正文
第一步:
pom.xml 添加核心依賴包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--使用Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.9.1</version>
</dependency>
第二步:
建立RedissonConfig.java:
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 java.io.IOException;
/**
* redisson bean管理
*/
@Configuration
public class RedissonConfig {
/**
* Redisson用戶端注冊
* 單機模式
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient createRedissonClient() throws IOException {
// Config config = new Config();
// SingleServerConfig singleServerConfig = config.useSingleServer();
// singleServerConfig.setAddress("redis://127.0.0.1:6379");
// singleServerConfig.setPassword("12345");
// singleServerConfig.setTimeout(3000);
// return Redisson.create(config)
// 本例子使用的是yaml格式的配置檔案,讀取使用Config.fromYAML,如果是Json檔案,則使用Config.fromJSON
Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson-config.yml"));
return Redisson.create(config);
}
/**
* 主從模式 哨兵模式
*
**/
/* @Bean
public RedissonClient getRedisson() {
RedissonClient redisson;
Config config = new Config();
config.useMasterSlaveServers()
//可以用"rediss://"來啟用SSL連接配接
.setMasterAddress("redis://***(主伺服器IP):6379").setPassword("web2017")
.addSlaveAddress("redis://***(從伺服器IP):6379")
.setReconnectionTimeout(10000)
.setRetryInterval(5000)
.setTimeout(10000)
.setConnectTimeout(10000);//(連接配接逾時,機關:毫秒 預設值:3000);
// 哨兵模式config.useSentinelServers().setMasterName("mymaster").setPassword("web2017").addSentinelAddress("***(哨兵IP):26379", "***(哨兵IP):26379", "***(哨兵IP):26380");
redisson = Redisson.create(config);
return redisson;
}*/
}
上面配置裡可以使用傳值方式去連接配接redis,也可以選擇從配置檔案擷取參數,反正方式多樣。
redisson-config.yml 檔案(如果沒有密碼設定為null即可):
#Redisson配置
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: 12345
clientName: null
database: 7 #選擇使用哪個資料庫0~15
idleConnectionTimeout: 10000
pingTimeout: 1000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
reconnectionTimeout: 3000
failedAttempts: 3
subscriptionsPerConnection: 5
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 32
connectionPoolSize: 64
dnsMonitoringInterval: 5000
#dnsMonitoring: false
threads: 0
nettyThreads: 0
codec:
class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"
第三步:
使用鎖,建立DistributeLocker.java :
import java.util.concurrent.TimeUnit;
/**
* @Author : JCccc
* @CreateTime : 2020/5/13
* @Description :
**/
public interface DistributeLocker {
/**
* 加鎖
* @param lockKey key
*/
void lock(String lockKey);
/**
* 釋放鎖
*
* @param lockKey key
*/
void unlock(String lockKey);
/**
* 加鎖鎖,設定有效期
*
* @param lockKey key
* @param timeout 有效時間,預設時間機關在實作類傳入
*/
void lock(String lockKey, int timeout);
/**
* 加鎖,設定有效期并指定時間機關
* @param lockKey key
* @param timeout 有效時間
* @param unit 時間機關
*/
void lock(String lockKey, int timeout, TimeUnit unit);
/**
* 嘗試擷取鎖,擷取到則持有該鎖傳回true,未擷取到立即傳回false
* @param lockKey
* @return true-擷取鎖成功 false-擷取鎖失敗
*/
boolean tryLock(String lockKey);
/**
* 嘗試擷取鎖,擷取到則持有該鎖leaseTime時間.
* 若未擷取到,在waitTime時間内一直嘗試擷取,超過waitTime還未擷取到則傳回false
* @param lockKey key
* @param waitTime 嘗試擷取時間
* @param leaseTime 鎖持有時間
* @param unit 時間機關
* @return true-擷取鎖成功 false-擷取鎖失敗
*/
boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit)
throws InterruptedException;
/**
* 鎖是否被任意一個線程鎖持有
* @param lockKey
* @return true-被鎖 false-未被鎖
*/
boolean isLocked(String lockKey);
//lock.isHeldByCurrentThread()的作用是查詢目前線程是否保持此鎖定
boolean isHeldByCurrentThread(String lockKey);
}
再是建立這個鎖接口的實作類 ,RedissonDistributeLocker.java :
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
* redisson實作分布式鎖接口
*/
public class RedissonDistributeLocker implements DistributeLocker {
private RedissonClient redissonClient;
public RedissonDistributeLocker(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Override
public void lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
public void lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.MILLISECONDS);
}
@Override
public void lock(String lockKey, int timeout, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
}
@Override
public boolean tryLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock();
}
@Override
public boolean tryLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit) throws InterruptedException {
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock(waitTime, leaseTime, unit);
}
@Override
public boolean isLocked(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
return lock.isLocked();
}
@Override
public boolean isHeldByCurrentThread(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
return lock.isHeldByCurrentThread();
}
}
然後建立鎖操作工具類,RedissonLockUtils.java :
import java.util.concurrent.TimeUnit;
/**
* redisson鎖工具類
*/
public class RedissonLockUtils {
private static DistributeLocker locker;
public static void setLocker(DistributeLocker locker) {
RedissonLockUtils.locker = locker;
}
public static void lock(String lockKey) {
locker.lock(lockKey);
}
public static void unlock(String lockKey) {
locker.unlock(lockKey); }
public static void lock(String lockKey, int timeout) {
locker.lock(lockKey, timeout);
}
public static void lock(String lockKey, int timeout, TimeUnit unit) {
locker.lock(lockKey, timeout, unit);
}
public static boolean tryLock(String lockKey) {
return locker.tryLock(lockKey);
}
public static boolean tryLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit) throws InterruptedException {
return locker.tryLock(lockKey, waitTime, leaseTime, unit);
}
public static boolean isLocked(String lockKey) {
return locker.isLocked(lockKey);
}
public static boolean isHeldByCurrentThread(String lockKey) {
return locker.isHeldByCurrentThread(lockKey);
}
}
然後注意,我們為了友善使用,我們在RedissonConfig.java 配置類裡面添加注入bean代碼(将RedissonDistributeLocker 交給Spring管理,且将RedissonDistributeLocker交給我們的操作鎖工具類),在RedissonConfig.java 加上:
@Bean
public RedissonDistributeLocker redissonLocker(RedissonClient redissonClient) {
RedissonDistributeLocker locker = new RedissonDistributeLocker(redissonClient);
RedissonLockUtils.setLocker(locker);
return locker;
}
到這裡,其實我們已經整合完畢Redisson了。
接下來我們來實作AOP 注解方式去給接口加鎖和釋放鎖。
1. 建立自定義注解 ,RedissonLockAnnotation.java:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 分布式鎖自定義注解
*/
@Target(ElementType.METHOD) //注解在方法
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLockAnnotation {
/**
* 指定組成分布式鎖的key
*/
String lockRedisKey();
}
2.建立配合注解使用的aop類,RedissonLockAop.java(自定義注解的路徑改成你自己項目的路徑):
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.TimeUnit;
/**
* 分布式鎖的 aop
*/
@Aspect
@Component
@Slf4j
public class RedissonLockAop {
/**
* 切點,攔截被 @RedissonLockAnnotation 修飾的方法
*/
@Pointcut("@annotation(com.bsapple.vshop.redisson.RedissonLockAnnotation)")
public void redissonLockPoint() {
}
@Around("redissonLockPoint()")
@ResponseBody
public String checkLock(ProceedingJoinPoint pjp) throws Throwable {
//目前線程名
String threadName = Thread.currentThread().getName();
log.info("線程{}------進入分布式鎖aop------", threadName);
//擷取參數清單
Object[] objs = pjp.getArgs();
//因為隻有一個JSON參數,直接取第一個
JSONObject param = (JSONObject) objs[0];
//擷取該注解的執行個體對象
RedissonLockAnnotation annotation = ((MethodSignature) pjp.getSignature()).
getMethod().getAnnotation(RedissonLockAnnotation.class);
//生成分布式鎖key的鍵名,以逗号分隔
String lockRedisKey = annotation.lockRedisKey();
StringBuffer keyBuffer = new StringBuffer();
if (StringUtils.isEmpty(lockRedisKey)) {
log.info("線程{} lockRedisKey設定為空,不加鎖", threadName);
pjp.proceed();
return "NULL LOCK";
} else {
//生成分布式鎖key
String[] keyPartArray = lockRedisKey.split(",");
for (String keyPart : keyPartArray) {
keyBuffer.append(param.getString(keyPart));
}
String key = keyBuffer.toString();
log.info("線程{} 鎖的key={}", threadName, key);
//擷取鎖 3000 等到擷取鎖的時間 leaseTime 擷取鎖後持有時間 時間機關 MILLISECONDS:毫秒
if (RedissonLockUtils.tryLock(key, 3000, 5000, TimeUnit.MILLISECONDS)) {
try {
log.info("線程{} 擷取鎖成功", threadName);
return (String) pjp.proceed();
} finally {
if (RedissonLockUtils.isLocked(key)) {
log.info("key={}對應的鎖被持有,線程{}",key, threadName);
if (RedissonLockUtils.isHeldByCurrentThread(key)) {
log.info("目前線程 {} 保持鎖定", threadName);
RedissonLockUtils.unlock(key);
log.info("線程{} 釋放鎖", threadName);
}
}
}
} else {
log.info("線程{} 擷取鎖失敗", threadName);
return " GET LOCK FAIL";
}
}
}
}
測試&分析:
第一步,寫一個測試接口,使用分布式鎖注解,來看看效果:
TestController.java:
import com.alibaba.fastjson.JSONObject;
import org.redisson.api.RBucket;
import org.redisson.api.RMap;
import org.redisson.api.RSet;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @Author : JCccc
* @CreateTime : 2020/5/13
* @Description :
**/
@RestController
public class TestController {
@Autowired
private RedissonClient redissonClient;
@PostMapping(value = "testLock", consumes = "application/json")
@RedissonLockAnnotation(lockRedisKey = "productName,platFormName")
public String testLock(@RequestBody JSONObject params) throws InterruptedException {
/**
* 分布式鎖key=params.getString("productName")+params.getString("platFormName");
* productName 産品名稱 platFormName 平台名稱 如果都一緻,那麼分布式鎖的key就會一直,那麼就能避免并發問題
*/
//TODO 業務處理
try {
System.out.println("接收到的參數:"+params.toString());
System.out.println("執行相關業務...");
System.out.println("執行相關業務.....");
System.out.println("執行相關業務......");
} catch (InterruptedException e) {
System.out.println("已進行日志記錄");
}
return "success";
}
}
第二步,調用接口,打斷點看看整體的流程:

調用接口,
繼續往下看,
繼續往下,
此刻可以看到redis資料庫裡,
生成了對應的鎖:
然後業務執行完後,在finally裡會對目前的産品key進行釋放鎖,
ok,以上就是使用Redisson實作分布式鎖的相關代碼介紹,接下來簡單介紹下,使用redisson去操作各常用集合資料。
方法的使用介紹:
1. 操作 String :
@GetMapping("/testData")
public void testData() {
// 插入 字元串
RBucket<String> keyObj = redissonClient.getBucket("keyStr");
keyObj.set("testStr", 300l, TimeUnit.SECONDS);
//查詢 字元串
RBucket<String> keyGet = redissonClient.getBucket("keyStr");
System.out.println(keyGet.get());
}
存入成功:
取出成功:
2.操作list:
// 插入 list
List<Integer> list = redissonClient.getList("list");
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//查詢 list
List<Integer> listGet = redissonClient.getList("list");
System.out.println(listGet.toString());
3.操作map:
//插入 map
RMap<Object, Object> addMap = redissonClient.getMap("addMap");
addMap.put("man1","a");
addMap.put("man2","b");
addMap.put("man3","c");
//查詢 map
RMap<Object, Object> mapGet = redissonClient.getMap("addMap");
System.out.println(mapGet.get("man1"));
4.操作set:
//設定 set
RSet<Object> testSet = redissonClient.getSet("testSet");
testSet.add("S");
testSet.add("D");
testSet.add("F");
testSet.add("G");
//查詢 set
RSet<Object> setGet = redissonClient.getSet("testSet");
System.out.println(setGet.readAll());
其餘更多的操作方法,可以點進去源碼看:
PS:覺得使用RedissonClient存值麻煩的,其實可以使用以前的方法,同時使用redissonClient和正常整合redis使用StringRedisTemplate/RedisTemplate 是完全不沖突的。