前言
该篇是基于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 是完全不冲突的。