前言
通过上一篇文章的话我们就基本已经掌握了xxl-job的使用以及一些基础知识那么这篇文章的话我们就来说一下其的底层的实现也就是源码部分,其实它的源码相对于Spring框架而言的话简直可以说是少的可伶,那么废话少说直接开始整吧!!!
知识回顾:
通过上篇文章的内容我们知道整个xxl-job的话其实就是分为两个部分:调度中心以及执行器,那么就是调度中心如果发现一个任务需要执行的话那么就会发送请求给执行器那么执行器就会根据请求中的信息然后就会执行这个方法.那么这个的话就是会有一些疑问:调度中心是怎么知道在这个时候有哪些任务需要被执行,执行器是怎么知道应该要运行哪个方法尤其是有多个@xxlJob注释的方法,那么接下来的话我们就是对其的源码进行简单的分析,之后的话这些问题就是会迎刃而解了
执行器(core):
我们就是先来看看执行器的相关的源码当然这个也过程包括我们启动整个SpringBoot项目:
1.SpringBoot项目启动
就是将我们的相关的配置类的话进行自动配置然后的话就是将这个配置类(xxlJobConfig)注册到Spring容器里面

1.2.接下来的话就是重点看看这个XxlJobSpringExecutor的源码,当我查看的时候我们就是可以看见的就是两个方法但是我们重点就是看的就是initJobHandlerMethodRepository方法,这里就是多说一句就是说我们可以发现这个类的就是实现的是SmartInitializingSingleton接口,这个接口的话我们是不是在说到BeanPostProcessor后置处理器的时候进行说明的如果不明白的话可以看看我的这个博客Spring源码分析之后置处理器 BeanPostProcessor-CSDN博客

1.3.initJobHandlerMethodRepository:
这个方法其实就是特别重要因为我们都知道从Spring获得Bean对象的话那么就是通过getBean方法那么这个也是一样获得相对应的Bean对象之后,然后就是通过遍历其中的所有的被@xxlJob注解的方法然后的话就是将这个方法放在Map中进行保存,但是我们都知道我们任务真正进行执行的时候其实是通过我们配置的JobHandler,所以就是进行一个的分解其实就是将这些注解里面的内容(这个里面就是JobHandler)以及相对应的方法进行存储,那么这个的话也就是通过Map进行存储的(这个逻辑的话主要就是通过(registJobHandler)
//这个就是存储xxl-job注解以及其所对应的方法
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);
}
});

1.4.registJobHandler:
这个方法的话就是通过上一步的操作等到的Method方法以及@xxlJob的注解然后将JobHandler已经其对应的方法等一些放在一个Map进行一个保存那么这个执行的话我们其实就是能够想到了就是通过JobHandler然后通过get方法找到相对应的方法以及一些Bean对象,之后执行的话就是通过反射机制其实就是能够完成了
registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);
return jobHandlerRepository.put(name, jobHandler);
}
private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
1.5.我们就是来看一看是不是我们上面说的进行的


2.执行器的启动:
我们都知道当我们Spring项目是需要进行依赖Core的,所以当我们我们这个启动的时候那么Core也会相对应的进行启动如果我们的调度平台中的执行器管理进行新增之后就是会出现执行器的名字以及其的地址那么接下来我们就是看看这个是怎么进行实现的

2.1进行注册
我们点击这个方法进行查看的时候最后就是会跳转到EmbedServer#start中,那么这个我们就是这个服务就是一个Netty的服务最重要的就是其中的startRegistry

2.2startRegistry:
这个方法的话就是真正实现了注册功能,那么这个的话也是特别简单的一个实现就是通过传入的调度中心的地址然后向它发送Http请求之后,这个请求里面就是包括的就是这个执行器的名字,Ip以及端口号,然后就是每30s就进行一次注册(这个就是进行一个死循环)
public static final int BEAT_TIMEOUT = 30;
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);

调度中心(admin):
通过上面的内容我们知道了执行器启动之后做的最重要的两件事情: 一.就是获得所有的jobHandler以及其所对应的方法 二.就是向调度中心进行注册自己的相关的信息那么接下来的话我们就是来看一看调度中心是怎么处理这个注册,怎么知道一个时间需要执行哪些任务的
1.处理执行器的注册
根据执行器注册的对应得网址的话我们就是在Admin里面找到相对应的Controller,之后进行定位到
JobRegistryHelper中的Registry方法

JobRegistryHelper#Registry
这个方法的逻辑就是在于在数据库里面修改其的更新时间就行了,但是如果注册完成之后的话如果出现网络抖动等现象的话那么这个执行器的信息应该怎么进行处理这个的话后面进行说明

但是所以我们一定要记住一定要自己在任务管理那里进行自己手动添加任务,这样的话这个定时任务的执行才会生效
2.调度中心的启动
调度中心启动的话就是会在XxlJobAdminConfig这个里面完成一些初始化工作的进行

2.1JobRegistryHelper.getInstance().start();
这个就是自己也在后台的管理平台上面也添加了任务之后进行的处理,这个我觉得里面最重要做的事情移除哪些没有在规定时间范围之内发起注册请求的执行器所以,就是会进行监控也就是说如果一个执行器没有在90s之内发送注册请求的话那么就是会进行一个移除的操作
List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());

2.2JobFailMonitorHelper.getInstance().start():
这个话就是用于解决如果任务调度失败的话那么这个时候进行的处理,因为我们也配置了重试次数以及相关的负责人以及负责人相关的邮箱的地址那么就会进行发送相关的失败的消息

3.JobScheduleHelper.getInstance().start()(重点)
这个里面的话主要就是包含着两个线程一个线程是scheduleThread以及ringThread这个就是核心线程做的事情不同但是非常重要,这个就是任务触发就是整个xxlJob的核心之一
3.1scheduleThread:
这个线程的话主要就是查哪些任务需要被执行,这个触发时间的话也是要进行分情况进行讨论,下面的话就是看看源码(这个主要就是查看重点的源码):
long nowTime = System.currentTimeMillis();
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
}
}
else if (nowTime > jobInfo.getTriggerNextTime()) {
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
pushTimeRing(ringSecond, jobInfo.getId());
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
else {
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
pushTimeRing(ringSecond, jobInfo.getId());
}
}
这个其实拿现在的时间(now)和任务下一次触发的时间(next)进行对比:
3.1.1.第一种情况next<now-5:这个就是又可能就是调度中心出现问题没有进行进行触发那么这个时候就会使用到我们配置的调度过期策略:是忽略还是说立即执行一次,如果说我这个任务一天执行一次的话那么就是会选择立即执行一次如果说我们的这个任务是每5分钟执行一次的话那么就是配置忽略 第二种情况 now-5<next<now+5:这个时候就会将这个任务立刻执行一次到那时更新时间之后就是还会出现一种那么就是 next<now+5那么这个的话那么就会将这个任务以及器执行的时间通过时间轮进行存储 第三种情况 now<next<now+5:这个任务的话那就是将要执行的任务那么这个时候就会之间将这个任务的执行时间以及任务的本身放在时间轮里面进行存储. 可以看看下图:

3.2 ringThread:
这个的话就是通过当前的时间然后将这个时间点需要执行的任务从时间轮里面全部进行获得然后就是发送给执行器进行执行
List<Integer> ringItemData = new ArrayList<>();
int nowSecond = Calendar.getInstance().get(Calendar.SECOND);
for (int i = 0; i < 2; i++) {
List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
if (tmpData != null) {
ringItemData.addAll(tmpData);
}
}
for (int jobId: ringItemData) {
//这个就是执行在这个时间需要执行的任务
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
}
对于时间轮的话就是说一个Map的结构
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
trigger():
经历过几次跳转之后最终到达的就是说XxlJobTriggerr#Trigger,那么这个的话其实主要就是说通过线程池进行处理
//获得这个要执行的任务的所有的信息
XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
最后我们发现这个通过JobID在表中就是查到了所有要执行的任务通过processTrigger进行任务的执行然后经过跳转之后就是到达了runExecutor方法,然后的话整个流程也就明白了
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);



3685

被折叠的 条评论
为什么被折叠?



