问题:项目部署到两台服务器上定时任务重复执行造成数据问题
解决办法:使用redis锁的形式进行解决,每次只允许一台服务器执行
reids锁+AOP切面,将加锁部分抽象出来,然后利用自定义注解的形式方便以后对其他地方进行加锁处理。
上代码:
切面类:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
/**
* redis 分布式锁
*/
@Aspect
@Slf4j
@Component
public class CacheLockAspect{
@Resource
private RedisUtil<String> redisUtil;
/**
* 分布式锁的key
*/
private static final String KEY_PREFIX_LOCK = "CACHE_LOCK_ASPECT:";
@Around("@annotation(com.xxx.xxxx.xxxx.CacheLock)")
public void cacheLockPoint(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method cacheMethod = signature.getMethod();
if(null == cacheMethod){
log.info("未获取到使用方法 pjp: {}",pjp);
return;
}
String lockKey = cacheMethod.getAnnotation(CacheLock.class).lockedKey();
Integer timeOut = cacheMethod.getAnnotation(CacheLock.class).expireTime();
boolean release = cacheMethod.getAnnotation(CacheLock.class).release();
if(StringUtils.isBlank(lockKey)){
log.error("method:{}, 锁名称为空!",cacheMethod);
return;
}
try {
//这里使用jedis的setnx方法,不存在则添加,成功返回1,不成功返回0
if (redisUtil.setnx(KEY_PREFIX_LOCK+lockKey, lockKey, timeOut).equals(1L)){
//对这个key加上有效期
redisUtil.expire(KEY_PREFIX_LOCK+lockKey, timeOut);
log.info("method:{} 获得锁:{},开始运行!",Thread.currentThread(),KEY_PREFIX_LOCK+lockKey);
pjp.proceed();
return;
}
log.info("method:{} 未获取锁:{},运行失败!",Thread.currentThread(),KEY_PREFIX_LOCK+lockKey);
//不需要释放锁
release = false;
} catch (Throwable e) {
log.error("method:{},运行错误!",cacheMethod,e);
}finally {
if(release){
log.info("method:{} 执行完成释放锁:{}",cacheMethod,KEY_PREFIX_LOCK+lockKey);
redisUtil.delete(KEY_PREFIX_LOCK+lockKey);
}
}
}
}
自定义注解:
import java.lang.annotation.*;
/**自定义注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheLock {
String lockedKey() default ""; //redis锁key
int expireTime() default 100; //key在redis里存在的时间 单位:秒
boolean release() default false; //是否在方法执行完成之后释放锁
}
测试:在需要加锁的方法上加上自定义注解即可
//测试服务类
@Slf4j
@Component
public class TestServiceImpl {
@CacheLock(lockedKey = "testLock", expireTime = 50)
@Scheduled(cron = "0 0/1 * * * ? ")
public void test1() {
log.info("方法1开始执行!!!!");
}
@CacheLock(lockedKey = "testLock", expireTime = 50)
@Scheduled(cron = "0 0/1 * * * ? ")
public void test2() {
log.info("方法2执行了====");
}
}
补充:
redis工具类,实现setnx方法
public class RedisUtil{
@Autowired
public RedisTemplate redisTemplate;
public boolean setnx(String key, String val, Long expireTime){
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
Boolean aBoolean = connection.setNX(key.getBytes(), val.getBytes());
if (aBoolean){
// 设置过期时间
this.expire(key, expireTime);
return true;
}
Long expireTime1 = this.getExpireTime(key);
if (expireTime1 == -1L) {
// 过期时间为-1, 删除该key
this.deleteObject(key);
}
return false;
});
}
}
参考文章:利用Redis分布式锁解决集群服务器定时任务重复执行问题__陈同学_的博客-CSDN博客_redis分布式锁定时任务