SpringBoot:解决定时任务多机器部署问题
基于Redis解决定时任务多机器部署问题
·
背景:
项目中 定时任务的场景很多,单机部署没问题,多台机器部署就会有重复执行问题,那么如何解决这类问题!
方案:
1、 拆分,单独拆分出来,单独跑一个应用
2、基于aop拦截处理(抢占执行),只要有一个执行,其它都不执行(前提:服务器时间一致)
第一种方案就不用说了,说下基于redis如何实现:
定时任务Aop一样可以处理的,多台同个任务类似抢占,先抢到的则打标识记录在Redis中,根据有无标识去执行任务
代码:
1.自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {
//锁前缀
String lockPrefix() default "redisLock:";
//键
String lockKey() default "";
//默认超时时间 秒
long timeOut() default 6;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
2.AOP部分
@Aspect
@Component
public class RedisLockAspect {
private static final Integer Max_RETRY_COUNT = 3;
private static final String LOCK_PRE_FIX = "lockPreFix";
private static final String LOCK_KEY = "lockKey";
private static final String TIME_OUT = "timeOut";
private static final int PROTECT_TIME = 2 << 11;//4096
private static final Logger log = LoggerFactory.getLogger(RedisLock.class);
@Autowired
private CommonRedisHelper commonRedisHelper;
@Pointcut("@annotation(com.zanzung.common.annotation.RedisLock)")
public void redisLockAspect() {
}
@Around(value = "redisLockAspect()")
public void lockAroundAction(ProceedingJoinPoint proceeding) {
//获取redis锁
boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());
if (flag) {
try {
proceeding.proceed();
Thread.sleep(PROTECT_TIME);
} catch (Throwable throwable) {
throw new RuntimeException("分布式锁执行发生异常" + throwable.getMessage(), throwable);
} finally {
// 删除锁
this.delLock(proceeding);
}
} else {
log.info("其他系统正在执行此项任务");
}
}
//获取锁
private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {
//获取注解中的参数
Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
String key = (String) annotationArgs.get(LOCK_KEY);
long expire = (long) annotationArgs.get(TIME_OUT);
if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {
throw new RuntimeException("RedisLock,锁前缀|锁名未设置");
}
if (commonRedisHelper.setNx(lockPrefix, key, expire)) {
log.info("@@@-> " + Thread.currentThread().getName() + "已获取到锁");
return true;
} else {
//如果当前时间与锁的时间差,大于保护时间,则强制删除锁(防止死锁)
long createTime = commonRedisHelper.getLockValue(lockPrefix, key);
if ((currentTime - createTime) > (expire * 1000 + PROTECT_TIME)) {
count++;
if (count > Max_RETRY_COUNT) {
return false;
}
commonRedisHelper.delete(lockPrefix, key);
getLock(proceeding, count, currentTime);
}
log.error("正在执行的定时任务Key:" + key);
log.info("@@@-> " + Thread.currentThread().getName() + "获取锁失败");
return false;
}
}
/**
* 删除锁
*/
private void delLock(ProceedingJoinPoint proceeding) {
Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
String key = (String) annotationArgs.get(LOCK_KEY);
commonRedisHelper.delete(lockPrefix, key);
}
/**
* 获取锁参数
*
* @param proceeding
* @return
*/
private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
Class target = proceeding.getTarget().getClass();
Method[] methods = target.getMethods();
String methodName = proceeding.getSignature().getName();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Map<String, Object> result = new HashMap<String, Object>();
RedisLock redisLock = method.getAnnotation(RedisLock.class);
result.put(LOCK_PRE_FIX, redisLock.lockPrefix());
result.put(LOCK_KEY, redisLock.lockKey());
result.put(TIME_OUT, redisLock.timeUnit().toSeconds(redisLock.timeOut()));
return result;
}
}
return new HashMap<>();
}
}
3. CommonRedisHelper 部分
@Component
public class CommonRedisHelper {
@Autowired
RedisTemplate<Object, Object> redisTemplate;
/**
* 添加分布式锁
*/
public boolean setNx(String track, String sector, long timeout) {
boolean flag = false;
ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();
flag = valueOperations.setIfAbsent(track + sector, System.currentTimeMillis());
if (flag) {
valueOperations.set(track + sector, getLockValue(track, sector), timeout, TimeUnit.SECONDS);
}
return flag;
}
/**
* 删除锁
*
* @param lockPrefix 前缀
* @param key 键
*/
public void delete(String lockPrefix, String key) {
redisTemplate.delete(lockPrefix + key);
}
/**
* 查询锁
*
* @return 写锁时间
*/
public long getLockValue(String track, String sector) {
ValueOperations valueOperations = redisTemplate.opsForValue();
return (long) valueOperations.get(track + sector);
}
}
4. 方法上加上自定义注解
END
更多推荐
已为社区贡献8条内容
所有评论(0)