需求:实现分布式锁
需求:分布式应用防止重复执行
解答:
本文会使用两种方式实现:
- 自己写代码实现,体现实现思路,但是无法满足运行中极端特殊场景
- 使用redission开源框架,简单便捷,能满足极端特殊场景。
方案一:自己jedis实现
实现:
利用Redis的set方法实现分布式锁
//核心代码Boolean ret = redisTemplate.opsForValue().setIfAbsent(lockKey, true, Duration.ofSeconds(timeout));
使用Spring的AOP实现
1、 SpringBoot项目,引入redis框架依赖
<!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
2、 注解
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface NotRepeat {/*** 资源锁的key值*/String value();/*** 超时时间,防止死锁,超时自动释放锁单位秒* 默认10分钟*/int timeout() default 10 * 60;}
3、 切面
@Aspect@Configuration@Slf4jpublic class NotRepeatInterceptor {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Around("execution(public * *(..)) && @annotation(com.xxx.zhilian.lsy.common.notrepeat.NotRepeat)")public Object interceptor(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();NotRepeat notRepeatAnno = method.getAnnotation(NotRepeat.class);String lockKey = "notRepeat-" + notRepeatAnno.value();int timeout = notRepeatAnno.timeout();try {//--预留 任务生命周期钩子---//---1. 任务启动前---//分布式锁,保持单一定时任务执行log.info("定时任务-启动-增加锁,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());Boolean ret = redisTemplate.opsForValue().setIfAbsent(lockKey, true, Duration.ofSeconds(timeout));if (ret != null && ret) {try {//任务脚本log.info("开始执行任务,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());Object proceed = pjp.proceed();log.info("任务执行完毕,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());return proceed;//---2. 任务启动后---} catch (Throwable e) {throw new RuntimeException(StrUtil.format("定时任务-发生异常,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now()),e);} finally {//释放锁redisTemplate.delete(lockKey);log.info("释放锁,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now());}//---3. 任务结束完---} else {throw new NotRepeatException(StrUtil.format("定时任务-正在运行-本次取消执行,lockkey:{},当前时间:{}", lockKey, LocalDateTime.now()));}} catch (Throwable e) {if (e instanceof NotRepeatException) {throw new NotRepeatException(e);}throw new RuntimeException("系统异常",e);}}}
3、Redis配置
@Configuration@EnableCachingpublic class RedisConfig {/*** 配置Redis的序列化规则,用Jackson2JsonRedisSerializer替换默认的jdkSerializer** @param connectionFactory RedisConnectionFactory* @return RedisTemplate*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 配置连接工厂redisTemplate.setConnectionFactory(connectionFactory);// 使用Jackson2JsonRedisSerialize替换默认序列化Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置key和value的序列化规则redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}}
4、 自定义异常
@Slf4jpublic class NotRepeatException extends RuntimeException{public NotRepeatException() {}public NotRepeatException(String message) {super(message);}public NotRepeatException(String message, Throwable cause) {super(message, cause);}public NotRepeatException(Throwable cause) {super(cause);}public NotRepeatException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}}
5、 使用示例
@GetMapping("test")@NotRepeat("test")//饱和式防重复注解public R<String> test() {return R.ok();}
方案二:使用redisson框架实现
1、 引入redisson框架
<!-- redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.15.4</version></dependency>
2、 核心代码
并发改同步示例:
@AutowiredRedissonClient redisson;RLock lock = redisson.getLock(key);try {log.debug("增加锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());lock.lock();log.debug("锁定-开始执行,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());Object proceed = pjp.proceed();log.debug("锁定-执行结束,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());return proceed;} catch (Throwable t) {throw new BizException(t.getMessage(), t);} finally {//释放锁lock.unlock();log.debug("释放锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());}
不允许并发,并发请求抛异常示例:
@AutowiredRedissonClient redisson;RLock lock = redisson.getLock(key);boolean tryLock = false;try {log.debug("增加锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());tryLock = lock.tryLock();if (tryLock) {log.debug("锁定-开始执行,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());Object proceed = pjp.proceed();log.debug("锁定-执行结束,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());return proceed;} else {throw new BizException("请求过于频繁,锁定执行中!" );}} catch (Throwable t) {throw new BizException(t.getMessage(), t);} finally {if (tryLock) {lock.unlock();log.debug("释放锁,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());}else {log.debug("未锁定-无需释放,lockKey:[{}],当前时间:{}", key, LocalDateTime.now());}}
