前言
Redisson 在基于 NIO 的 Netty 框架上,充分的利⽤了 Redis 键值数据库提供的⼀系列优势,在Java 实⽤⼯具包中常⽤接⼝的基础上,为使⽤者提供了⼀系列具有分布式特性的常⽤⼯具类。使得原本作为协调单机多线程并发程序的⼯具包获得了协调分布式多机多线程并发系统的能⼒,⼤⼤降低了设计和研发⼤规模分布式系统的难度。同时结合各富特⾊的分布式服务,更进⼀步简化了分布式环境中程序相互之间的协作。
1、redisson工作原理
2、看门狗原理
A服务先运行,在运行B服务,还没释放A的锁,A就挂了,会不会死锁呢?
答:没有导致死锁,因为底层有看门狗机制
默认指定锁时间为30s(看门狗时间)
锁的自动续期:若是业务超长,运行期间自动给锁上新的 30s,不用担心业务时间过长,锁就自动过期
加锁的业务只要运行完成,就不会给当前锁续期,及时不手动解锁,锁默认在30s 后自动删除。
3、spring boot与redisson的整合
3.1、添加库存服务:
stock-service
3.2、添加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.4.0</version> </dependency>--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.19.1</version> </dependency>
3.3、添加配置
3.3.1、单机
redisson: addr: singleAddr: host: redis://localhost:6379 password: 123456 database: 0 pool-size: 10
3.3.2、集群
redisson: addr: cluster: hosts: redis://47.96.11.185: 6370,...,redis://47.96.11.185:6373 password : 123456
3.3.3、主从
redisson: addr: masterAndSlave: masterhost: redis : //47.96.11.185 : 6370 slavehosts: redis://47.96.11.185: 6371,redis://47.96.11.185:6372 password : 123456 database : 0
3.4、配置RedissonClient
3.4.1、单机
/** * 配置RedissonClient */ @Configuration public class RedissonConfig { @Value("${redisson.addr.singleAddr.host}") private String host; @Value("${redisson.addr.singleAddr.password}") private String password; @Value("${redisson.addr.singleAddr.database}") private int database; @Value("${redisson.addr.singleAddr.pool-size}") private int poolSize; @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer().setAddress(host) .setPassword(password) .setDatabase(database) .setConnectionPoolSize(poolSize) .setConnectionMinimumIdleSize(poolSize); return Redisson.create(config); } }
3.4.2、集群
/** * 配置RedissonClient */ @Configuration public class RedissonConfig { @Value("${redisson.addr.cluster.hosts}") private String hosts; @Value("${redisson.addr.cluster.password}") private String password; /** * 集群模式 * * @return */ @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useClusterServers().addNodeAddress(hosts.split("[,]")) .setPassword(password) .setScanInterval(2000) .setMasterConnectionPoolSize(10000) .setSlaveConnectionPoolSize(10000); return Redisson.create(config); } }
3.4.3、集群
/** * 配置RedissonClient */ @Configuration public class RedissonConfig { @Value("${redisson.addr.masterAndSlave.masterhost}") private String masterhost; @Value("${redisson.addr.masterAndSlave.slavehosts}") private String slavehosts; @Value("${redisson.addr.masterAndSlave.password}") private String password; @Value("${redisson.addr.masterAndSlave.database}") private int database; /** * 主从模式 * * @return */ @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useMasterSlaveServers() .setMasterAddress(masterhost) .addSlaveAddress(slavehosts.split("[,]")) .setPassword(password) .setDatabase(database) .setMasterConnectionPoolSize(10000) .setSlaveConnectionPoolSize(10000); return Redisson.create(config); } }
3.5、Redisson的使用
- 获取锁 —— 公平锁和⾮公平锁
// 获取公平锁
RLock lock = redissonClient . getFairLock ( skuId );
// 获取⾮公平锁
RLock lock = redissonClient . getLock ( skuId ); - 加锁 —— 阻塞锁和⾮阻塞锁
// 阻塞锁(如果加锁成功之后,超时时间为 30s ;加锁成功开启看⻔狗,剩 5s 延⻓过期时间)
lock . lock ();
// 阻塞锁(如果加锁成功之后,设置⾃定义 20s 的超时时间)
lock . lock ( 20 , TimeUnit . SECONDS );
// ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功默认超时间为 30s )
boolean b = lock . tryLock ( 3 , TimeUnit . SECONDS );
// ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功设置⾃定义超时间为 20s )
boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS ); - 释放锁
lock . unlock (); - 应⽤示例
// 公平⾮阻塞锁
RLock lock = redissonClient . getFairLock ( skuId );
boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS ); - 减库存加锁案例
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/stock") public class StockController { @Autowired private RedissonClient redissonClient; @GetMapping("/reduceStock") public void reduceStock(@RequestParam String productId){ // 获取⾮公平锁 RLock lock = this.redissonClient.getLock("stock:" + productId); // 阻塞锁(如果加锁成功之后,设置⾃定义 20s 的超时时间) lock.lock(30, TimeUnit.SECONDS); System.out.println("加锁成功." + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(25); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("解锁成功." + Thread.currentThread().getName()); lock.unlock(); } } }
测试:浏览器发起两次两次减库存
http://localhost:8099/stock/reduceStock?productId=001
3.6、aop实现分布式锁
3.6.1、定义注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DistributeLock { /** * 参数下标 * @return */ int[] lockIndex() default {-1} ; /** * 锁的等待时间 * @return */ long waitTime() default 3000; /** * 时间单位 * @return */ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; }
3.6.2、定义切面
/** * 定义分布式锁的切面 */ @Component @Aspect public class DistributeLockAspect { @Autowired private RedissonClient redissonClient; @Around(value = "@annotation(lock)") public void distibuteLock(ProceedingJoinPoint proceedingJoinPoint, DistributeLock lock){ Signature signature = proceedingJoinPoint.getSignature(); StringBuilder stringBuilder = new StringBuilder(); //方法所属的类 String declaringTypeName = signature.getDeclaringTypeName(); String name = signature.getName(); stringBuilder.append(declaringTypeName); stringBuilder.append(name); //获取调用方法的参数 Object[] args = proceedingJoinPoint.getArgs(); int[] ints = lock.lockIndex(); if(args != null) { final int length = args.length; if (length >0) { //考虑下标越界 for (int anInt : ints) { //把合法下标值放到sb if (anInt >= 0 && anInt < length){ stringBuilder.append(JSON.toJSONString(args[anInt])); } } } } //将方法的信息转成md5,作为锁的标识 String key = SecureUtil.md5(stringBuilder.toString()); //获取锁 RLock rLock = redissonClient.getLock(key); //从注解获取时间单位 TimeUnit timeUnit = lock.timeUnit(); //从注解等待时间 long waitTime = lock.waitTime(); //执行业务代码 try { //加锁 rLock.tryLock(waitTime,timeUnit); System.out.println("成功加锁。" + Thread.currentThread().getName()); proceedingJoinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); }finally { //解锁 rLock.unlock(); System.out.println("成功解锁。" + Thread.currentThread().getName()); } } }
注解的使用:
@DistributeLock(lockIndex = {0,1},waitTime = 3,timeUnit = TimeUnit.SECONDS)