首先,我也没有做过什么分布式的任务调度框架,我只是这几天做定时任务,为了解决高可用的特性,弄了一个简化版的分布式任务调度。其实分布式任务调度很简单,说白了在单机上做的话,就需要利用lock或者锁了。而多服务器了之后,就需要提供一个分布式锁即可,但是这样会在每个方法里面去显式地去写出一段代码,例如我使用redis来实现的话。
boolean haveGotLock = redisUtil.set(key,value,nx,ex,timeout);
if(!haveGotLock)
return;
这种,其实是重复的工作,因此直接考虑aop来做。
先放一段我想达到的效果
@DistributedSchedule(lockKey="yourJobKey")
public void yourJob(Object args..){}
这样的形式就可以了。
1.首先得有注解才行
/**
* 分布式 调度的注解
*
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedSchedule {
/**
* key的过期时间 不写的话,默认20秒过期
* @return
*/
long timeout() default 20;
/**
* key的名字必填
*/
String lockKey();
/**
* value 非必填
*/
String lockValue() default "";
}
2.那么既然写了注解,就得有相应的aop才行
/**
* 分布式任务调度
* @author limaojie
*
*/
@Aspect
public class DistributedScheduleAspect {
//依赖 org.slf4j.Logger
private static final Logger logger = LoggerFactory.getLogger(DistributedScheduleAspect.class);
/**切入点*/
@Pointcut("@annotation(com.util.annotation.DistributedSchedule)")
public void distributedScheduleAspectPointcut() {};
/**这是必须要的,是分布式锁的定义超类*/
@Autowired
private ISchduleLock schduleLock;
/**
* 切入
* @throws Throwable
*/
@Around("distributedScheduleAspectPointcut()")
public Object methodsAnnotatedWithDistributedSchedule(ProceedingJoinPoint joinPoint) throws Throwable {
beforeTryLock();
if(null == schduleLock)
throw new DistributedScheduleFailException("you need check your schduleLock,because the schduleLock is null");
boolean isLocked=false;
try {
//通过反射去获取到该方法注解的属性值
DistributedSchedule schedule =((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(DistributedSchedule.class);
String lockKey = schedule.lockKey();
long timeout = schedule.timeout();
String lockValue = schedule.lockValue();
//当value不设置的时候就随机生成一个uuid
//其实value弄成uuid都有点浪费了,完全可以设成0
if(null == lockValue || "".equals(lockValue))
lockValue=UUID.randomUUID().toString();
//当获取分布式锁成功的时候
//isLocked就应该是true
isLocked = schduleLock.tryLock(lockKey, timeout, lockValue);
}catch (Exception e) {
logger.error("your schduleLock had happened some error:{}",e);
//throw e 我想的是,可以再做一个捕获异常
//当依赖的分布式锁崩溃了,这个时候可以进行一些系统提醒,例如发日志等
throw e;
//这里为什么不绕过,是因为,许多定时任务并不允许在一个时间点多次发生
}
//成功就继续调用方法
if(isLocked) {
Object result = joinPoint.proceed();
//后置方法
afterJobDone();
return result;
} else
return null;
}
protected void beforeTryLock() {
//if you need, you can overload
}
protected void afterJobDone() {
//if you need, you can overload
}
}
3.注意一下ISchduleLock这个接口
/**
* 分布式锁的定义类
*
*/
public interface ISchduleLock {
/**
* 锁住
*/
boolean tryLock(String key,long timeout,String value);
/**
* 释放锁
*/
boolean releaseLock(String key);
}
其实上面,我只用到了分布式锁的方法,而没有使用到释放锁的操作
那么如果你想使用的话,就可以写一个类,不管是redis还是zookeeper等等都可以,然后实现我这个接口,重写这两个方法即可。然后注入给spring容器即可。
最后我在把我的configuration贴出来
@Configuration
//这个注解是为了考虑到用户继承了distributedScheduleAspect 实现了一些功能而放弃默认的
@ConditionalOnMissingBean(name= {"distributedScheduleAspect"})
public class DistributedScheduledConfig {
//因为DistributedSecheduleAspect必须要依赖一个分布式锁的实现,因此写了一个ConditionalOnBean
@Bean
@ConditionalOnBean(name="schduleLock")
public DistributedScheduleAspect distributedScheduleAspect() {
return new DistributedScheduleAspect();
}
}