xxl-job学习
文章目录
设计思想
- 调度中心:将调度行为抽象形成"调度中心"公共平台,而平台自身并不承担业务逻辑,"调度中心"负责发起调度请求。
- 执行器将任务抽象成分散的JobHandler,交由"执行器"统一管理,"执行器"负责接收调度请求并执行对应的JobHandler中业务逻辑。
- 因此,"调度"和"任务"两部分可以相互解耦,提高系统整体稳定性和扩展性;
简单总结
xxl-lob分布式的任务调度框架
核心思想是:任务的调度和任务的执行分离,任务的调度中心化统一调度、任务的执行分布式执行
系统模块:调度中心分为执行器管理、任务管理(调度配置CRON、运行配置、路由配置、失败策略)
关键技术:
1.调度器和执行器如何通信,通过内嵌Netty作为http服务器,调度中心提供注册、回调等api接口、执行中心:提供心跳、心跳繁忙、运行等api接口
2.调度器如何调度:间隔5秒扫描任务表xxl_job_info的下次触发时间消息当前时间加5秒(trigger_next_time < now + 5),再把5秒内将要执行的任务放入时间轮等待调度
3.调度器如何保证并发调度:基于mysql悲观锁机制xxl_job_lock表加锁
4.执行器如何注册到调度中心:执行器30秒定时注册
5.执行器如何自动扫描:扫描所有xxlJob注解的方法并维护,XxlJobSpringExecutor.afterSingletonsInstantiated()
调度平台源码分析
- 调度管理平台如何发现需要调度的任务:通过xxl_job_lock表加锁并且间隔5秒扫描任务表xxl_job_info的下次触发时间消息当前时间加5秒(trigger_next_time < now + 5),再把5秒内将要执行的任务放入时间轮等待调度,这里直接回答解决了问题1、2、3,核心代码逻辑JobScheduleHelper#start
java
//定时调度线程
scheduleThread = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
} catch (InterruptedException e) {
}
// pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
while (!scheduleThreadToStop) {
// Scan Job
long start = System.currentTimeMillis();
boolean preReadSuc = true;
try {
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
//加锁避免分布式并发执行
preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();
long nowTime = System.currentTimeMillis();
//扫描下次触发时间小于当前时间加5秒的任务
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (scheduleList!=null && scheduleList.size()>0) {
for (XxlJobInfo jobInfo: scheduleList) {
//下次触发时间可能已过期5秒,可能是调度平台宕机一直没有刷新下次触发时间
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
//刷新下次触发时间
refreshNextValidTime(jobInfo, new Date());
} else if (nowTime > jobInfo.getTriggerNextTime()) {
//下次触发时间过期但在5秒以内,立即触发执行
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
//刷新下次触发时间
refreshNextValidTime(jobInfo, new Date());
//下次触发时间如果将在5秒以内执行的任务,放入时间轮等待触发,这样是因为上面刷新了下次触发时间所以增加判断
if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
// 1、计算时间轮的秒
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2、放入时间轮
pushTimeRing(ringSecond, jobInfo.getId());
// 3、刷新下次触发时间
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
} else {
// 下次触发时间如果将在5秒以内执行的任务
// 1、make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2、push time ring
pushTimeRing(ringSecond, jobInfo.getId());
// 3、fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3、更新任务信息,主要是更新任务的下次触发时间
for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
}
} else {
preReadSuc = false;
}
} catch (Exception e) {
} finally {
// 省略
}
long cost = System.currentTimeMillis()-start;
if (cost < 1000) { // scan-overtime, not wait
try {
// 如果扫描到任务每秒执行, 没有扫描到则间隔一个扫描周期5秒执行
TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
} catch (InterruptedException e) {
}
}
}
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
}
});
scheduleThread.setDaemon(true);
scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
scheduleThread.start();
//时间轮执行线程
ringThread = new Thread(new Runnable() {
@Override
public void run() {
while (!ringThreadToStop) {
// align second
try {
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
} catch (InterruptedException e) {
}
try {
List<Integer> ringItemData = new ArrayList<>();
int nowSecond = Calendar.getInstance().get(Calendar.SECOND);
// 避免处理耗时太长,跨过刻度,向前校验一个刻度; 因为这里是从时间轮移除所以不会重复执行
for (int i = 0; i < 2; i++) {
//从时间轮获取当前的秒 对应的 任务列表,rangData类型:Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
if (tmpData != null) {
ringItemData.addAll(tmpData);
}
}
if (ringItemData.size() > 0) {
for (int jobId: ringItemData) {
//触发执行
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
}
// clear
ringItemData.clear();
}
} catch (Exception e) {
}
}
}
});
ringThread.setDaemon(true);
ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
ringThread.start();
}
执行器源码分析(业务端)
- 扫描所有xxlJob注册的方法,XxlJobSpringExecutor#afterSingletonsInstantiated
java
public void afterSingletonsInstantiated() {
// init JobHandler Repository (for method)
initJobHandlerMethodRepository(applicationContext);
}
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
if (applicationContext == null) {
return;
}
// init job handler from method
String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = applicationContext.getBean(beanDefinitionName);
Map<Method, XxlJob> annotatedMethods = null; // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
try {
annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
new MethodIntrospector.MetadataLookup<XxlJob>() {
@Override
public XxlJob inspect(Method method) {
return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
}
});
} catch (Throwable ex) {
}
if (annotatedMethods==null || annotatedMethods.isEmpty()) {
continue;
}
for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
Method executeMethod = methodXxlJobEntry.getKey();
XxlJob xxlJob = methodXxlJobEntry.getValue();
// regist
registJobHandler(xxlJob, bean, executeMethod);
}
}
}
protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
if (xxlJob == null) {
return;
}
String name = xxlJob.value();
Class<?> clazz = bean.getClass();
String methodName = executeMethod.getName();
// registry jobhandler
registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
}
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
//执行任务的name和处理器映射
return jobHandlerRepository.put(name, jobHandler);
}