目录:
调度中心端如何进行任务管理及调度
分布式调度平台XXL-JOB源码分析-执行器端
----------------------------------------------------------
架构图:
xxl 上图是我们要进行源码分析的2.1版本的整体架构图。其分为两大块,调度中心和执行器,本文先分析调度中心,也就是xxl-job-admin这个包的代码。
在application.properties配置正确的数据库连接信息后,直接启动XxlJobAdminApplication即可。
配置类XxlJobAdminConfig,里面维护了一些调度中心端的配置数据。
XxlJobScheduler这个组件实现了InitializingBean接口,所以spring容器在初始化的时候会调用afterPropertiesSet方法,此方法如下:
第一步国际化相关。
第二步监控相关。
第三步失败重试相关。
第四步启动admin端服务,接收注册请求等。
第五步JobScheduleHelper调度器,死循环,在xxl_job_info表里取将要执行的任务,更新下次执行时间的,调用JobTriggerPoolHelper类,来给执行器发送调度任务的.
这个类就是死循环从xxl_job_info表中取出未来5秒内要执行的任务,进行调度分发。
启动了两个守护线程,先来看scheduleThread。
死循环内的代码如上图,首先利用for update语句进行获取任务的资格锁定,再去获取未来5秒内即将要执行的任务。
ringData是以0到59的整数为key,以jobId集合为value的Map集合。这个集合数据的处理逻辑,就在我们第二个守护线程ringThread中。
while (!ringThreadToStop) {
2 try {
3 // second data
4 List ringItemData = new ArrayList<>();
5 int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
6 for (int i = 0; i < 2; i++) {
7 List tmpData = ringData.remove( (nowSecond+60-i)%60 );
8 if (tmpData != null) {
9 ringItemData.addAll(tmpData);
10 }
11 }
12 // ring trigger
13 logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
14 if (ringItemData!=null && ringItemData.size()>0) {
15 // do trigger
16 for (int jobId: ringItemData) {
17 // do trigger
18 JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
19 }
20 // clear
21 ringItemData.clear();
22 }
23 } catch (Exception e) {
24 if (!ringThreadToStop) {
25 logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
26 }
27 }
28 // next second, align second
29 try {
30 TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000);
31 } catch (InterruptedException e) {
32 if (!ringThreadToStop) {
33 logger.error(e.getMessage(), e);
34 }
35 }
36 }
minTim属性,作用待明确
jobTimeoutCountMap属性,计数,key为jobId,value使用AtomicInteger计数。
helper静态变量指向自己本身,提供外部静态方法调用。
重要方法,向两种线程池其中之一提交调度任务,进行调度,引出XxlJobTrigger这个类,一路跟进去.
XxlJobExecutorApplication为我们执行器的启动项,其中有个XxlJobConfig的配置项,发现其中有个属性为adminAddresses,这个就是我们调度中心的地址。
声明了init方法为start,点进来,
它又实现了ApplicationContextAware接口,用来保存spring的上下文信息。
它还有个父类XxlJobExecutor,暂时未找到其他子类。
程序开始执行start方法:
第一步,调用了本类的私有方法,这个方法就是把JobHandler的实现类取出来,再调用registJobHandler(name, handler)进行注册。
1.日志处理器初始化
2.向adminBizList字段中放入XxlRpcReferenceBean返回的代理类,作用之后会单独开一篇注册心跳的文章说明。
3.任务日志清除
4.任务结果回调处理线程
5.启动另一个执行器的执行线程XxlRpcProviderFactory这个类是XXl其他的开源项目,自研RPC
看名字就知道这个类是可以返回Rpc调用服务提供端的工厂类,接上文,看他的initRpcProvider方法。
此时的ServiceRegistry为ExecutorServiceRegistry,调用其start,以30秒的间隔和调度中心进行心跳通知,然后调用server的start方法,此时server为NettyHttpServer.
从serviceData中拿到我们之前调用addService方法添加的服务实现类,这里是ExecutorBizImpl,这里反射调用的方法是run。
如果没有正在执行此任务的线程,那就调用XxlJobExecutor.registJobThread()启动一个线程,最后将任务数据推送给这个可能是从jobThreadRepository获取到的也可能是新创建的线程,如下图。
至此,执行器完成了启动,暴露ExecutorBiz服务,接收任务调度数据TriggerParam,并在JobThread线程中完成任务配置的业务handler的执行。