需求:实现分布式锁
需求:分布式应用防止重复执行
解答:
本文会使用两种方式实现:
- 自己写代码实现,体现实现思路,但是无法满足运行中极端特殊场景
- 使用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
@Documented
public @interface NotRepeat {
/**
* 资源锁的key值
*/
String value();
/**
* 超时时间,防止死锁,超时自动释放锁单位秒
* 默认10分钟
*/
int timeout() default 10 * 60;
}
3、 切面
@Aspect
@Configuration
@Slf4j
public class NotRepeatInterceptor {
@Autowired
private 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
@EnableCaching
public class RedisConfig {
/**
* 配置Redis的序列化规则,用Jackson2JsonRedisSerializer替换默认的jdkSerializer
*
* @param connectionFactory RedisConnectionFactory
* @return RedisTemplate
*/
@Bean
public 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、 自定义异常
@Slf4j
public 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、 核心代码
并发改同步示例:
@Autowired
RedissonClient 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());
}
不允许并发,并发请求抛异常示例:
@Autowired
RedissonClient 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());
}
}