需求:实现分布式锁

需求:分布式应用防止重复执行
解答:
本文会使用两种方式实现:

  1. 自己写代码实现,体现实现思路,但是无法满足运行中极端特殊场景
  2. 使用redission开源框架,简单便捷,能满足极端特殊场景。

推荐企业项目中,使用方案2,但是方案1提供设计思路。

方案一:自己jedis实现

实现:
利用Redis的set方法实现分布式锁

  1. //核心代码
  2. Boolean ret = redisTemplate
  3. .opsForValue()
  4. .setIfAbsent(lockKey, true, Duration.ofSeconds(timeout));

使用Spring的AOP实现
1、 SpringBoot项目,引入redis框架依赖

  1. <!--redis-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>

2、 注解

  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. @Documented
  5. public @interface NotRepeat {
  6. /**
  7. * 资源锁的key值
  8. */
  9. String value();
  10. /**
  11. * 超时时间,防止死锁,超时自动释放锁单位秒
  12. * 默认10分钟
  13. */
  14. int timeout() default 10 * 60;
  15. }

3、 切面

  1. @Aspect
  2. @Configuration
  3. @Slf4j
  4. public class NotRepeatInterceptor {
  5. @Autowired
  6. private RedisTemplate<String, Object> redisTemplate;
  7. @Around("execution(public * *(..)) && @annotation(com.xxx.zhilian.lsy.common.notrepeat.NotRepeat)")
  8. public Object interceptor(ProceedingJoinPoint pjp) {
  9. MethodSignature signature = (MethodSignature) pjp.getSignature();
  10. Method method = signature.getMethod();
  11. NotRepeat notRepeatAnno = method.getAnnotation(NotRepeat.class);
  12. String lockKey = "notRepeat-" + notRepeatAnno.value();
  13. int timeout = notRepeatAnno.timeout();
  14. try {
  15. //--预留 任务生命周期钩子---
  16. //---1. 任务启动前---
  17. //分布式锁,保持单一定时任务执行
  18. log.info("定时任务-启动-增加锁,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());
  19. Boolean ret = redisTemplate.opsForValue().setIfAbsent(lockKey, true, Duration.ofSeconds(timeout));
  20. if (ret != null && ret) {
  21. try {
  22. //任务脚本
  23. log.info("开始执行任务,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());
  24. Object proceed = pjp.proceed();
  25. log.info("任务执行完毕,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());
  26. return proceed;
  27. //---2. 任务启动后---
  28. } catch (Throwable e) {
  29. throw new RuntimeException(StrUtil.format("定时任务-发生异常,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now()),e);
  30. } finally {
  31. //释放锁
  32. redisTemplate.delete(lockKey);
  33. log.info("释放锁,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());
  34. }
  35. //---3. 任务结束完---
  36. } else {
  37. throw new NotRepeatException(StrUtil.format("定时任务-正在运行-本次取消执行,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now()));
  38. }
  39. } catch (Throwable e) {
  40. if (e instanceof NotRepeatException) {
  41. throw new NotRepeatException(e);
  42. }
  43. throw new RuntimeException("系统异常",e);
  44. }
  45. }
  46. }

3、Redis配置

  1. @Configuration
  2. @EnableCaching
  3. public class RedisConfig {
  4. /**
  5. * 配置Redis的序列化规则,用Jackson2JsonRedisSerializer替换默认的jdkSerializer
  6. *
  7. * @param connectionFactory RedisConnectionFactory
  8. * @return RedisTemplate
  9. */
  10. @Bean
  11. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
  12. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  13. // 配置连接工厂
  14. redisTemplate.setConnectionFactory(connectionFactory);
  15. // 使用Jackson2JsonRedisSerialize替换默认序列化
  16. Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
  17. ObjectMapper objectMapper = new ObjectMapper();
  18. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  19. objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  20. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  21. // 设置key和value的序列化规则
  22. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  23. redisTemplate.setKeySerializer(new StringRedisSerializer());
  24. redisTemplate.afterPropertiesSet();
  25. return redisTemplate;
  26. }
  27. }

4、 自定义异常

  1. @Slf4j
  2. public class NotRepeatException extends RuntimeException{
  3. public NotRepeatException() {
  4. }
  5. public NotRepeatException(String message) {
  6. super(message);
  7. }
  8. public NotRepeatException(String message, Throwable cause) {
  9. super(message, cause);
  10. }
  11. public NotRepeatException(Throwable cause) {
  12. super(cause);
  13. }
  14. public NotRepeatException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
  15. super(message, cause, enableSuppression, writableStackTrace);
  16. }
  17. }

5、 使用示例

  1. @GetMapping("test")
  2. @NotRepeat("test")//饱和式防重复注解
  3. public R<String> test() {
  4. return R.ok();
  5. }

方案二:使用redisson框架实现

1、 引入redisson框架

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

2、 核心代码

并发改同步示例:

  1. @Autowired
  2. RedissonClient redisson;
  3. RLock lock = redisson.getLock(key);
  4. try {
  5. log.debug("增加锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  6. lock.lock();
  7. log.debug("锁定-开始执行,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  8. Object proceed = pjp.proceed();
  9. log.debug("锁定-执行结束,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  10. return proceed;
  11. } catch (Throwable t) {
  12. throw new BizException(t.getMessage(), t);
  13. } finally {
  14. //释放锁
  15. lock.unlock();
  16. log.debug("释放锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  17. }

不允许并发,并发请求抛异常示例:

  1. @Autowired
  2. RedissonClient redisson;
  3. RLock lock = redisson.getLock(key);
  4. boolean tryLock = false;
  5. try {
  6. log.debug("增加锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  7. tryLock = lock.tryLock();
  8. if (tryLock) {
  9. log.debug("锁定-开始执行,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  10. Object proceed = pjp.proceed();
  11. log.debug("锁定-执行结束,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  12. return proceed;
  13. } else {
  14. throw new BizException("请求过于频繁,锁定执行中!" );
  15. }
  16. } catch (Throwable t) {
  17. throw new BizException(t.getMessage(), t);
  18. } finally {
  19. if (tryLock) {
  20. lock.unlock();
  21. log.debug("释放锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  22. }else {
  23. log.debug("未锁定-无需释放,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());
  24. }
  25. }