Spring Boot 集成 Redisson分布式锁(注解版)

野性酷女 2024-03-16 15:27 118阅读 0赞

" class="reference-link">0093c6bfd7e04ffa847c73d067b82280.png

Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库,可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面,例如分布式锁、分布式集合、分布式事件发布和订阅等。本篇是一个使用 Redisson 实现分布式锁的详细示例,在这个示例中,我们定义了DistributedLock注解,它可以标注在方法上,配合DistributedLockAspect切面以及IDistributedLock分布式锁封装的接口,来实现redisson 分布式锁的 API 调用。

Spring Boot 集成 Redisson

1、在 pom.xml 文件中添加 Redisson 的相关依赖

  1. <dependency>
  2. <groupId>org.redisson</groupId>
  3. <artifactId>redisson-spring-boot-starter</artifactId>
  4. <version>3.16.1</version>
  5. </dependency>

2、在 application.yml 中配置 Redisson单机模式 的连接信息和相关参数

  1. spring:
  2. redis:
  3. host: localhost
  4. port: 6379
  5. password: null
  6. redisson:
  7. codec: org.redisson.codec.JsonJacksonCodec
  8. threads: 4
  9. netty:
  10. threads: 4
  11. single-server-config:
  12. address: "redis://localhost:6379"
  13. password: null
  14. database: 0

3、Redission还支持主从、集群、哨兵配置

  1. //主从模式
  2. spring:
  3. redis:
  4. sentinel:
  5. master: my-master
  6. nodes: localhost:26379,localhost:26389
  7. password: your_password
  8. redisson:
  9. master-slave-config:
  10. master-address: "redis://localhost:6379"
  11. slave-addresses: "redis://localhost:6380,redis://localhost:6381"
  12. password: ${spring.redis.password}
  13. // 集群模式
  14. spring:
  15. redis:
  16. cluster:
  17. nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
  18. password: your_password
  19. redisson:
  20. cluster-config:
  21. node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
  22. password: ${spring.redis.password}
  23. // 哨兵模式
  24. spring:
  25. redis:
  26. sentinel:
  27. master: my-master
  28. nodes: localhost:26379,localhost:26389
  29. password: your_password
  30. redisson:
  31. sentinel-config:
  32. master-name: my-master
  33. sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
  34. password: ${spring.redis.password}

本地封装Redisson 分布式锁

1、定义IDistributedLock分布式锁接口

  1. public interface IDistributedLock {
  2. /**
  3. * 获取锁,默认30秒失效,失败一直等待直到获取锁
  4. *
  5. * @param key 锁的key
  6. * @return 锁对象
  7. */
  8. ILock lock(String key);
  9. /**
  10. * 获取锁,失败一直等待直到获取锁
  11. *
  12. * @param key 锁的key
  13. * @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
  14. * @param unit {@code lockTime} 参数的时间单位
  15. * @param fair 是否公平锁
  16. * @return 锁对象
  17. */
  18. ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
  19. /**
  20. * 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
  21. *
  22. * @param key 锁的key
  23. * @param tryTime 获取锁的最大尝试时间
  24. * @return
  25. * @throws Exception
  26. */
  27. ILock tryLock(String key, long tryTime) throws Exception;
  28. /**
  29. * 尝试获取锁,获取不到超时异常
  30. *
  31. * @param key 锁的key
  32. * @param tryTime 获取锁的最大尝试时间
  33. * @param lockTime 加锁的时间
  34. * @param unit {@code tryTime @code lockTime} 参数的时间单位
  35. * @param fair 是否公平锁
  36. * @return
  37. * @throws Exception
  38. */
  39. ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
  40. /**
  41. * 解锁
  42. *
  43. * @param lock
  44. * @throws Exception
  45. */
  46. void unLock(Object lock);
  47. /**
  48. * 释放锁
  49. *
  50. * @param lock
  51. * @throws Exception
  52. */
  53. default void unLock(ILock lock) {
  54. if (lock != null) {
  55. unLock(lock.getLock());
  56. }
  57. }
  58. }

2、IDistributedLock实现类

  1. @Slf4j
  2. @Component
  3. public class RedissonDistributedLock implements IDistributedLock {
  4. @Resource
  5. private RedissonClient redissonClient;
  6. /**
  7. * 统一前缀
  8. */
  9. @Value("${redisson.lock.prefix:bi:distributed:lock}")
  10. private String prefix;
  11. @Override
  12. public ILock lock(String key) {
  13. return this.lock(key, 0L, TimeUnit.SECONDS, false);
  14. }
  15. @Override
  16. public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
  17. RLock lock = getLock(key, fair);
  18. // 获取锁,失败一直等待,直到获取锁,不支持自动续期
  19. if (lockTime > 0L) {
  20. lock.lock(lockTime, unit);
  21. } else {
  22. // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
  23. lock.lock();
  24. }
  25. return new ILock(lock, this);
  26. }
  27. @Override
  28. public ILock tryLock(String key, long tryTime) throws Exception {
  29. return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
  30. }
  31. @Override
  32. public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
  33. throws Exception {
  34. RLock lock = getLock(key, fair);
  35. boolean lockAcquired;
  36. // 尝试获取锁,获取不到超时异常,不支持自动续期
  37. if (lockTime > 0L) {
  38. lockAcquired = lock.tryLock(tryTime, lockTime, unit);
  39. } else {
  40. // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
  41. lockAcquired = lock.tryLock(tryTime, unit);
  42. }
  43. if (lockAcquired) {
  44. return new ILock(lock, this);
  45. }
  46. return null;
  47. }
  48. /**
  49. * 获取锁
  50. *
  51. * @param key
  52. * @param fair
  53. * @return
  54. */
  55. private RLock getLock(String key, boolean fair) {
  56. RLock lock;
  57. String lockKey = prefix + ":" + key;
  58. if (fair) {
  59. // 获取公平锁
  60. lock = redissonClient.getFairLock(lockKey);
  61. } else {
  62. // 获取普通锁
  63. lock = redissonClient.getLock(lockKey);
  64. }
  65. return lock;
  66. }
  67. @Override
  68. public void unLock(Object lock) {
  69. if (!(lock instanceof RLock)) {
  70. throw new IllegalArgumentException("Invalid lock object");
  71. }
  72. RLock rLock = (RLock) lock;
  73. if (rLock.isLocked()) {
  74. try {
  75. rLock.unlock();
  76. } catch (IllegalMonitorStateException e) {
  77. log.error("释放分布式锁异常", e);
  78. }
  79. }
  80. }
  81. }

3、定义ILock锁对象

  1. import lombok.AllArgsConstructor;
  2. import lombok.Getter;
  3. import java.util.Objects;
  4. /**
  5. * <p>
  6. * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
  7. * </p>
  8. * @since 2023-06-08 16:57
  9. */
  10. @AllArgsConstructor
  11. public class ILock implements AutoCloseable {
  12. /**
  13. * 持有的锁对象
  14. */
  15. @Getter
  16. private Object lock;
  17. /**
  18. * 分布式锁接口
  19. */
  20. @Getter
  21. private IDistributedLock distributedLock;
  22. @Override
  23. public void close() throws Exception {
  24. if(Objects.nonNull(lock)){
  25. distributedLock.unLock(lock);
  26. }
  27. }
  28. }

4、注入IDistributedLock接口使用示例

  1. // 定义接口
  2. public interface IProductSkuSupplierMeasureService {
  3. /**
  4. * 保存SKU供应商供货信息
  5. *
  6. * @param dto
  7. * @return
  8. *
  9. Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);
  10. /**
  11. * 编辑SKU供应商供货信息
  12. *
  13. * @param dto
  14. * @return
  15. */
  16. Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
  17. }

手动释放锁示例

  1. // 实现类
  2. @Service
  3. public class ProductSkuSupplierMeasureServiceImpl
  4. implements IProductSkuSupplierMeasureService {
  5. @Resource
  6. private IDistributedLock distributedLock;
  7. @Override
  8. @Transactional(rollbackFor = Exception.class)
  9. public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
  10. // 手动释放锁
  11. String sku = dto.getSku();
  12. ILock lock = null;
  13. try {
  14. lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
  15. // 业务代码
  16. } catch (Exception e) {
  17. log.error("保存异常", e);
  18. throw new BaseException(e.getMessage());
  19. } finally {
  20. if (Objects.nonNull(lock)) {
  21. distributedLock.unLock(lock);
  22. }
  23. }
  24. return Boolean.TRUE;
  25. }
  26. }

使用try-with-resources 语法糖自动释放锁

  1. // 实现类
  2. @Service
  3. public class ProductSkuSupplierMeasureServiceImpl
  4. implements IProductSkuSupplierMeasureService {
  5. @Resource
  6. private IDistributedLock distributedLock;
  7. @Override
  8. @Transactional(rollbackFor = Exception.class)
  9. public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
  10. String sku = dto.getSku();
  11. // try-with-resources 语法糖自动释放锁
  12. try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
  13. if(Objects.isNull(lock)){
  14. throw new IdempotencyException("Duplicate request for method still in process");
  15. }
  16. // 业务代码
  17. } catch (Exception e) {
  18. log.error("异常", e);
  19. throw new BaseException(e.getMessage());
  20. }
  21. return Boolean.TRUE;
  22. }
  23. }

5、使用AOP切面实现分布式锁的绑定

定义DistributedLock注解

  1. @Target({ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface DistributedLock {
  5. /**
  6. * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key
  7. * 支持使用spEl表达式
  8. */
  9. String key();
  10. /**
  11. * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀
  12. */
  13. String keyPrefix() default "";
  14. /**
  15. * 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败
  16. */
  17. boolean tryLok() default false;
  18. /**
  19. * 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。
  20. *
  21. */
  22. long tryTime() default 0;
  23. /**
  24. * 加锁的时间,超过这个时间后锁便自动解锁
  25. */
  26. long lockTime() default 30;
  27. /**
  28. * tryTime 和 lockTime的时间单位
  29. */
  30. TimeUnit unit() default TimeUnit.SECONDS;
  31. /**
  32. * 是否公平锁,false:非公平锁,true:公平锁
  33. */
  34. boolean fair() default false;
  35. }

定义DistributedLockAspect Lock切面

  1. @Aspect
  2. @Slf4j
  3. public class DistributedLockAspect {
  4. @Resource
  5. private IDistributedLock distributedLock;
  6. /**
  7. * SpEL表达式解析
  8. */
  9. private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
  10. /**
  11. * 用于获取方法参数名字
  12. */
  13. private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
  14. @Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")
  15. public void distributorLock() {
  16. }
  17. @Around("distributorLock()")
  18. public Object around(ProceedingJoinPoint pjp) throws Throwable {
  19. // 获取DistributedLock
  20. DistributedLock distributedLock = this.getDistributedLock(pjp);
  21. // 获取 lockKey
  22. String lockKey = this.getLockKey(pjp, distributedLock);
  23. ILock lockObj = null;
  24. try {
  25. // 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常
  26. if (distributedLock.tryLok()) {
  27. if(distributedLock.tryTime() <= 0){
  28. throw new IdempotencyException("tryTime must be greater than 0");
  29. }
  30. lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
  31. } else {
  32. lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
  33. }
  34. if (Objects.isNull(lockObj)) {
  35. throw new IdempotencyException("Duplicate request for method still in process");
  36. }
  37. return pjp.proceed();
  38. } catch (Exception e) {
  39. throw e;
  40. } finally {
  41. // 解锁
  42. this.unLock(lockObj);
  43. }
  44. }
  45. /**
  46. * @param pjp
  47. * @return
  48. * @throws NoSuchMethodException
  49. */
  50. private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
  51. String methodName = pjp.getSignature().getName();
  52. Class clazz = pjp.getTarget().getClass();
  53. Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
  54. Method lockMethod = clazz.getMethod(methodName, par);
  55. DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
  56. return distributedLock;
  57. }
  58. /**
  59. * 解锁
  60. *
  61. * @param lockObj
  62. */
  63. private void unLock(ILock lockObj) {
  64. if (Objects.isNull(lockObj)) {
  65. return;
  66. }
  67. try {
  68. this.distributedLock.unLock(lockObj);
  69. } catch (Exception e) {
  70. log.error("分布式锁解锁异常", e);
  71. }
  72. }
  73. /**
  74. * 获取 lockKey
  75. *
  76. * @param pjp
  77. * @param distributedLock
  78. * @return
  79. */
  80. private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
  81. String lockKey = distributedLock.key();
  82. String keyPrefix = distributedLock.keyPrefix();
  83. if (StringUtils.isBlank(lockKey)) {
  84. throw new IdempotencyException("Lok key cannot be empty");
  85. }
  86. if (lockKey.contains("#")) {
  87. this.checkSpEL(lockKey);
  88. MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
  89. // 获取方法参数值
  90. Object[] args = pjp.getArgs();
  91. lockKey = getValBySpEL(lockKey, methodSignature, args);
  92. }
  93. lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
  94. return lockKey;
  95. }
  96. /**
  97. * 解析spEL表达式
  98. *
  99. * @param spEL
  100. * @param methodSignature
  101. * @param args
  102. * @return
  103. */
  104. private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
  105. // 获取方法形参名数组
  106. String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
  107. if (paramNames == null || paramNames.length < 1) {
  108. throw new IdempotencyException("Lok key cannot be empty");
  109. }
  110. Expression expression = spelExpressionParser.parseExpression(spEL);
  111. // spring的表达式上下文对象
  112. EvaluationContext context = new StandardEvaluationContext();
  113. // 给上下文赋值
  114. for (int i = 0; i < args.length; i++) {
  115. context.setVariable(paramNames[i], args[i]);
  116. }
  117. return expression.getValue(context).toString();
  118. }
  119. /**
  120. * SpEL 表达式校验
  121. *
  122. * @param spEL
  123. * @return
  124. */
  125. private void checkSpEL(String spEL) {
  126. try {
  127. ExpressionParser parser = new SpelExpressionParser();
  128. parser.parseExpression(spEL, new TemplateParserContext());
  129. } catch (Exception e) {
  130. log.error("spEL表达式解析异常", e);
  131. throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");
  132. }
  133. }
  134. }

定义分布式锁注解版启动元注解

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import({DistributedLockAspect.class})
  5. public @interface EnableDistributedLock {
  6. }

6、AOP切面实现分布式锁的绑定使用示例

启动类添加@EnableDistributedLock启用注解支持

  1. @SpringBootApplication
  2. @EnableDistributedLock
  3. public class BiCenterGoodsApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(BiCenterGoodsApplication.class, args);
  6. }
  7. }

@DistributedLock标注需要使用分布式锁的方法

  1. @ApiOperation("编辑SKU供应商供货信息")
  2. @PostMapping("/editSupplierInfo")
  3. //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
  4. @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
  5. public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {
  6. return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));
  7. }
  8. #dto.sku是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL 可以使用方法中的任何参数。SpEL表达式参考

从原理到实践,分析 Redis 分布式锁的多种实现方案(一)

发表评论

表情:
评论列表 (有 0 条评论,118人围观)

还没有评论,来说两句吧...

相关阅读