转载自:https://blog.csdn.net/Ascend1977/article/details/131126047
Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库,可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面,例如分布式锁、分布式集合、分布式事件发布和订阅等。本篇是一个使用 Redisson 实现分布式锁的详细示例,在这个示例中,我们定义了DistributedLock注解,它可以标注在方法上,配合DistributedLockAspect切面以及IDistributedLock分布式锁封装的接口,来实现redisson 分布式锁的 API 调用。
Spring Boot 集成 Redisson
1、在 pom.xml 文件中添加 Redisson 的相关依赖
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson-spring-boot-starter</artifactId>
- <version>3.16.1</version>
- </dependency>
2、在 application.yml 中配置 Redisson单机模式 的连接信息和相关参数
- spring:
- redis:
- host: localhost
- port: 6379
- password: null
- redisson:
- codec: org.redisson.codec.JsonJacksonCodec
- threads: 4
- netty:
- threads: 4
- single-server-config:
- address: "redis://localhost:6379"
- password: null
- database: 0
3、Redission还支持主从、集群、哨兵配置
- //主从模式
- spring:
- redis:
- sentinel:
- master: my-master
- nodes: localhost:26379,localhost:26389
- password: your_password
- redisson:
- master-slave-config:
- master-address: "redis://localhost:6379"
- slave-addresses: "redis://localhost:6380,redis://localhost:6381"
- password: ${spring.redis.password}
- // 集群模式
- spring:
- redis:
- cluster:
- nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
- password: your_password
- redisson:
- cluster-config:
- node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
- password: ${spring.redis.password}
- // 哨兵模式
- spring:
- redis:
- sentinel:
- master: my-master
- nodes: localhost:26379,localhost:26389
- password: your_password
- redisson:
- sentinel-config:
- master-name: my-master
- sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
- password: ${spring.redis.password}
本地封装Redisson 分布式锁
1、定义IDistributedLock分布式锁接口
- public interface IDistributedLock {
- /**
- * 获取锁,默认30秒失效,失败一直等待直到获取锁
- *
- * @param key 锁的key
- * @return 锁对象
- */
- ILock lock(String key);
- /**
- * 获取锁,失败一直等待直到获取锁
- *
- * @param key 锁的key
- * @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
- * @param unit {@code lockTime} 参数的时间单位
- * @param fair 是否公平锁
- * @return 锁对象
- */
- ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
- /**
- * 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
- *
- * @param key 锁的key
- * @param tryTime 获取锁的最大尝试时间
- * @return
- * @throws Exception
- */
- ILock tryLock(String key, long tryTime) throws Exception;
- /**
- * 尝试获取锁,获取不到超时异常
- *
- * @param key 锁的key
- * @param tryTime 获取锁的最大尝试时间
- * @param lockTime 加锁的时间
- * @param unit {@code tryTime @code lockTime} 参数的时间单位
- * @param fair 是否公平锁
- * @return
- * @throws Exception
- */
- ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
- /**
- * 解锁
- *
- * @param lock
- * @throws Exception
- */
- void unLock(Object lock);
- /**
- * 释放锁
- *
- * @param lock
- * @throws Exception
- */
- default void unLock(ILock lock) {
- if (lock != null) {
- unLock(lock.getLock());
- }
- }
- }
2、IDistributedLock实现类
- @Slf4j
- @Component
- public class RedissonDistributedLock implements IDistributedLock {
- @Resource
- private RedissonClient redissonClient;
- /**
- * 统一前缀
- */
- @Value("${redisson.lock.prefix:bi:distributed:lock}")
- private String prefix;
- @Override
- public ILock lock(String key) {
- return this.lock(key, 0L, TimeUnit.SECONDS, false);
- }
- @Override
- public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
- RLock lock = getLock(key, fair);
- // 获取锁,失败一直等待,直到获取锁,不支持自动续期
- if (lockTime > 0L) {
- lock.lock(lockTime, unit);
- } else {
- // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
- lock.lock();
- }
- return new ILock(lock, this);
- }
- @Override
- public ILock tryLock(String key, long tryTime) throws Exception {
- return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
- }
- @Override
- public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
- throws Exception {
- RLock lock = getLock(key, fair);
- boolean lockAcquired;
- // 尝试获取锁,获取不到超时异常,不支持自动续期
- if (lockTime > 0L) {
- lockAcquired = lock.tryLock(tryTime, lockTime, unit);
- } else {
- // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
- lockAcquired = lock.tryLock(tryTime, unit);
- }
- if (lockAcquired) {
- return new ILock(lock, this);
- }
- return null;
- }
- /**
- * 获取锁
- *
- * @param key
- * @param fair
- * @return
- */
- private RLock getLock(String key, boolean fair) {
- RLock lock;
- String lockKey = prefix + ":" + key;
- if (fair) {
- // 获取公平锁
- lock = redissonClient.getFairLock(lockKey);
- } else {
- // 获取普通锁
- lock = redissonClient.getLock(lockKey);
- }
- return lock;
- }
- @Override
- public void unLock(Object lock) {
- if (!(lock instanceof RLock)) {
- throw new IllegalArgumentException("Invalid lock object");
- }
- RLock rLock = (RLock) lock;
- if (rLock.isLocked()) {
- try {
- rLock.unlock();
- } catch (IllegalMonitorStateException e) {
- log.error("释放分布式锁异常", e);
- }
- }
- }
- }
3、定义ILock锁对象
- import lombok.AllArgsConstructor;
- import lombok.Getter;
- import java.util.Objects;
- /**
- * <p>
- * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
- * </p>
- * @since 2023-06-08 16:57
- */
- @AllArgsConstructor
- public class ILock implements AutoCloseable {
- /**
- * 持有的锁对象
- */
- @Getter
- private Object lock;
- /**
- * 分布式锁接口
- */
- @Getter
- private IDistributedLock distributedLock;
- @Override
- public void close() throws Exception {
- if(Objects.nonNull(lock)){
- distributedLock.unLock(lock);
- }
- }
- }
4、注入IDistributedLock接口使用示例
- // 定义接口
- public interface IProductSkuSupplierMeasureService {
- /**
- * 保存SKU供应商供货信息
- *
- * @param dto
- * @return
- *
- Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);
- /**
- * 编辑SKU供应商供货信息
- *
- * @param dto
- * @return
- */
- Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
- }
手动释放锁示例
- // 实现类
- @Service
- public class ProductSkuSupplierMeasureServiceImpl
- implements IProductSkuSupplierMeasureService {
- @Resource
- private IDistributedLock distributedLock;
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
- // 手动释放锁
- String sku = dto.getSku();
- ILock lock = null;
- try {
- lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
- if (Objects.isNull(lock)) {
- throw new BusinessException("Duplicate request for method still in process");
- }
- // 业务代码
- }catch (BusinessException e) {
- throw new BusinessException(e.getMessage());
- } catch (Exception e) {
- log.error("保存异常", e);
- throw new BusinessException (e.getMessage());
- } finally {
- if (Objects.nonNull(lock)) {
- distributedLock.unLock(lock);
- }
- }
- return Boolean.TRUE;
- }
- }
使用try-with-resources 语法糖自动释放锁
- // 实现类
- @Service
- public class ProductSkuSupplierMeasureServiceImpl
- implements IProductSkuSupplierMeasureService {
- @Resource
- private IDistributedLock distributedLock;
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
- String sku = dto.getSku();
- // try-with-resources 语法糖自动释放锁
- try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
- if(Objects.isNull(lock)){
- throw new BusinessException ("Duplicate request for method still in process");
- }
- // 业务代码
- }catch (BusinessException e) {
- throw new BusinessException (e.getMessage());
- } catch (Exception e) {
- log.error("修改异常", e);
- throw new BusinessException ("修改异常");
- }
- return Boolean.TRUE;
- }
- }
5、使用AOP切面实现分布式锁的绑定
定义DistributedLock注解
- @Target({ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface DistributedLock {
- /**
- * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key
- * 支持使用spEl表达式
- */
- String key();
- /**
- * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀
- */
- String keyPrefix() default "";
- /**
- * 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败
- */
- boolean tryLok() default false;
- /**
- * 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。
- *
- */
- long tryTime() default 0;
- /**
- * 加锁的时间,超过这个时间后锁便自动解锁
- */
- long lockTime() default 30;
- /**
- * tryTime 和 lockTime的时间单位
- */
- TimeUnit unit() default TimeUnit.SECONDS;
- /**
- * 是否公平锁,false:非公平锁,true:公平锁
- */
- boolean fair() default false;
- }
定义DistributedLockAspect Lock切面
- @Aspect
- @Slf4j
- public class DistributedLockAspect {
- @Resource
- private IDistributedLock distributedLock;
- /**
- * SpEL表达式解析
- */
- private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
- /**
- * 用于获取方法参数名字
- */
- private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
- @Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")
- public void distributorLock() {
- }
- @Around("distributorLock()")
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
- // 获取DistributedLock
- DistributedLock distributedLock = this.getDistributedLock(pjp);
- // 获取 lockKey
- String lockKey = this.getLockKey(pjp, distributedLock);
- ILock lockObj = null;
- try {
- // 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常
- if (distributedLock.tryLok()) {
- if(distributedLock.tryTime() <= 0){
- throw new IdempotencyException("tryTime must be greater than 0");
- }
- lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
- } else {
- lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
- }
- if (Objects.isNull(lockObj)) {
- throw new IdempotencyException("Duplicate request for method still in process");
- }
- return pjp.proceed();
- } catch (Exception e) {
- throw e;
- } finally {
- // 解锁
- this.unLock(lockObj);
- }
- }
- /**
- * @param pjp
- * @return
- * @throws NoSuchMethodException
- */
- private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
- String methodName = pjp.getSignature().getName();
- Class clazz = pjp.getTarget().getClass();
- Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
- Method lockMethod = clazz.getMethod(methodName, par);
- DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
- return distributedLock;
- }
- /**
- * 解锁
- *
- * @param lockObj
- */
- private void unLock(ILock lockObj) {
- if (Objects.isNull(lockObj)) {
- return;
- }
- try {
- this.distributedLock.unLock(lockObj);
- } catch (Exception e) {
- log.error("分布式锁解锁异常", e);
- }
- }
- /**
- * 获取 lockKey
- *
- * @param pjp
- * @param distributedLock
- * @return
- */
- private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
- String lockKey = distributedLock.key();
- String keyPrefix = distributedLock.keyPrefix();
- if (StringUtils.isBlank(lockKey)) {
- throw new IdempotencyException("Lok key cannot be empty");
- }
- if (lockKey.contains("#")) {
- this.checkSpEL(lockKey);
- MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
- // 获取方法参数值
- Object[] args = pjp.getArgs();
- lockKey = getValBySpEL(lockKey, methodSignature, args);
- }
- lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
- return lockKey;
- }
- /**
- * 解析spEL表达式
- *
- * @param spEL
- * @param methodSignature
- * @param args
- * @return
- */
- private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
- // 获取方法形参名数组
- String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
- if (paramNames == null || paramNames.length < 1) {
- throw new IdempotencyException("Lok key cannot be empty");
- }
- Expression expression = spelExpressionParser.parseExpression(spEL);
- // spring的表达式上下文对象
- EvaluationContext context = new StandardEvaluationContext();
- // 给上下文赋值
- for (int i = 0; i < args.length; i++) {
- context.setVariable(paramNames[i], args[i]);
- }
- Object value = expression.getValue(context);
- if (value == null) {
- throw new IdempotencyException("The parameter value cannot be null");
- }
- return value.toString();
- }
- /**
- * SpEL 表达式校验
- *
- * @param spEL
- * @return
- */
- private void checkSpEL(String spEL) {
- try {
- ExpressionParser parser = new SpelExpressionParser();
- parser.parseExpression(spEL, new TemplateParserContext());
- } catch (Exception e) {
- log.error("spEL表达式解析异常", e);
- throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");
- }
- }
- }
定义分布式锁注解版启动元注解
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Import({DistributedLockAspect.class})
- public @interface EnableDistributedLock {
- }
6、AOP切面实现分布式锁的绑定使用示例
启动类添加@EnableDistributedLock启用注解支持
- @SpringBootApplication
- @EnableDistributedLock
- public class BiCenterGoodsApplication {
- public static void main(String[] args) {
- SpringApplication.run(BiCenterGoodsApplication.class, args);
- }
- }
@DistributedLock标注需要使用分布式锁的方法
- @ApiOperation("编辑SKU供应商供货信息")
- @PostMapping("/editSupplierInfo")
- //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
- @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
- public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {
- return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));
- }
#dto.sku是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL 可以使用方法中的任何参数。SpEL表达式参考
从原理到实践,分析 Redis 分布式锁的多种实现方案(一)_Ascend JF的博客-CSDN博客
标签:lock,Redisson,return,String,Spring,Boot,param,key,distributedLock From: https://www.cnblogs.com/wanghengbin/p/18011134