Problem: The project is deployed to two servers and the timing tasks are repeatedly executed, causing data problems
Solution: Use the form of redis lock to solve, only one server is allowed to execute at a time
Reids lock + AOP aspect abstracts the locking part, and then uses the form of custom annotations to facilitate future locking of other places.
Above code:
Section class:
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);
}
}
}
}
Custom annotations:
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; //是否在方法执行完成之后释放锁
}
Test: Just add a custom annotation to the method that needs to be locked
//测试服务类
@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执行了====");
}
}
Replenish:
Redis tool class, implement setnx method
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;
});
}
}
Reference article: Using Redis distributed locks to solve the problem of repeated execution of cluster server timing tasks