该篇是基于springboot 项目整合 Redisson 实现对redis的操作。
1.以自定注解aop方式实现对接口使用分布式锁
2.使用RedissonClient对一些集合的常规操作,数据查询,存储等
第一步:
pom.xml 添加核心依赖包:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-data-redis
org.redisson
redisson
3.9.1
第二步:
新建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去操作各常用集合数据。
@GetMapping("/testData")
public void testData() {
// 插入 字符串
RBucket keyObj = redissonClient.getBucket("keyStr");
keyObj.set("testStr", 300l, TimeUnit.SECONDS);
//查询 字符串
RBucket keyGet = redissonClient.getBucket("keyStr");
System.out.println(keyGet.get());
}
存入成功:
取出成功:
// 插入 list
List list = redissonClient.getList("list");
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//查询 list
List listGet = redissonClient.getList("list");
System.out.println(listGet.toString());
//插入 map
RMap
//设置 set
RSet testSet = redissonClient.getSet("testSet");
testSet.add("S");
testSet.add("D");
testSet.add("F");
testSet.add("G");
//查询 set
RSet setGet = redissonClient.getSet("testSet");
System.out.println(setGet.readAll());
其余更多的操作方法,可以点进去源码看:
PS:觉得使用RedissonClient存值麻烦的,其实可以使用以前的方法,同时使用redissonClient和正常整合redis使用StringRedisTemplate/RedisTemplate 是完全不冲突的。