从定时器的选型,到透过源码看XXL-Job(下)

透过源码看xxl-job

(注:本文基于xxl-job最新版v2.0.2, quartz版本为 v2.3.1。 以下提到的调度中心均指xxl-job-admin项目)

上回说到,xxl-job是一个中心化的设计方案,分为了调度中心执行器两部分。其本质上仍然是对quartz的封装。那么,我们就分别通过“调度中心” 和 “执行器” 来看看它是怎么运作的。

调度中心

初始化

由于是spring boot应用,因此先从配置看起。

XxlJobDynamicSchedulerConfig

相关的初始化是在XxlJobDynamicSchedulerConfig中完成的,下面我们来看XxlJobDynamicSchedulerConfig源码。

@Configuration

public class XxlJobDynamicSchedulerConfig {   @Bean   public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){     SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();     schedulerFactory.setDataSource(dataSource);     schedulerFactory.setAutoStartup(true); // 自动启动     schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动     schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false     schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");     schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));     return schedulerFactory;   }   @Bean(initMethod = "start", destroyMethod = "destroy")   public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){     Scheduler scheduler = schedulerFactory.getScheduler();     XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();     xxlJobDynamicScheduler.setScheduler(scheduler);     return xxlJobDynamicScheduler;   } }

由上可知 **XxlJobDynamicSchedulerConfig** 主要是创建了 **SchedulerFactoryBean** 对**XxlJobDynamicScheduler** 对象。SchedulerFactoryBean创建调度器(Scheduler, 没错,它就是quartz中的Scheduler对象), XxlJobDynamicScheduler持有对Scheduler对象的引用。

那么,SchedulerFactoryBean 是如何创建 Scheduler的呢,接下来,我们再看看SchedulerFactoryBean 。

SchedulerFactoryBean

SchedulerFactoryBean实现了InitializingBean, 其主要初始化流程在 afterPropertiesSet 方法中。

@Override
public void afterPropertiesSet() throws Exception {
  if (this.dataSource == null && this.nonTransactionalDataSource != null) {
  this.dataSource = this.nonTransactionalDataSource;
  }

  if (this.applicationContext != null && this.resourceLoader == null) {
  this.resourceLoader = this.applicationContext;
  }

  // 初始化scheduler
  this.scheduler = prepareScheduler(prepareSchedulerFactory());
  try {
  registerListeners();
  registerJobsAndTriggers();
  } catch (Exception ex) {
    try {
    this.scheduler.shutdown(true);
    } catch (Exception ex2) {
      logger.debug("Scheduler shutdown exception after registration failure", ex2);
    }
    throw ex;
  }
}

以上,先通过prepareSchedulerFactory方法创建ScheduleFactory对象(quartz),再通过prepareScheduler方法创建Scheduler对象。

private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
    SchedulerFactory schedulerFactory = this.schedulerFactory;
    if (schedulerFactory == null) {
      //默认为StdSchedulerFactory
      schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
      if (schedulerFactory instanceof StdSchedulerFactory) {
      //解析处理配置
        initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
      } else if (this.configLocation != null || this.quartzProperties != null ||  this.taskExecutor != null || this.dataSource != null) {
        throw new IllegalArgumentException("StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
      }
    }
    return schedulerFactory;
}

接下来,我们看看prepareScheduler方法是怎么创建scheduler对象的。

protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) throws SchedulerException {
.........
  //已省略部分我们本次不用关心的代码 
  try {
    SchedulerRepository repository = SchedulerRepository.getInstance();
    synchronized (repository) {
      Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null);
      //通过schedulerFactory创建scheduler, 重点关注。前往quartz中一探究竟
      Scheduler newScheduler = schedulerFactory.getScheduler();
      if (newScheduler == existingScheduler) {
        throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " + "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!");
      }
      if (!this.exposeSchedulerInRepository) {
        SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName());
      }
      return newScheduler;
    }
  } finally {
    if (overrideClassLoader) {
      // Reset original thread context ClassLoader.
      currentThread.setContextClassLoader(threadContextClassLoader);
    }
  }
}

ok, 接着前往quartz中一探究竟。

StdSchedulerFactory

public Scheduler getScheduler() throws SchedulerException {
  if (cfg == null) {
    initialize();
  }
  SchedulerRepository schedRep = SchedulerRepository.getInstance();
  Scheduler sched = schedRep.lookup(getSchedulerName());

  //如果存在该对象,则直接返回
  if (sched != null) {
    if (sched.isShutdown()) {
      schedRep.remove(getSchedulerName());
    } else {
      return sched;
    }
  }

  //重点关注
  sched = instantiate();
  return sched;
}

下面就重点看看instantiate方法。

private Scheduler instantiate() throws SchedulerException {
....

  QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
  rsrcs.setName(schedName);
  rsrcs.setThreadName(threadName);
  rsrcs.setInstanceId(schedInstId);
  rsrcs.setJobRunShellFactory(jrsf);
  rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon);
  rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);
  rsrcs.setBatchTimeWindow(batchTimeWindow);
  rsrcs.setMaxBatchSize(maxBatchSize);
  rsrcs.setInterruptJobsOnShutdown(interruptJobsOnShutdown);
  rsrcs.setInterruptJobsOnShutdownWithWait(interruptJobsOnShutdownWithWait);
  rsrcs.setJMXExport(jmxExport);
  rsrcs.setJMXObjectName(jmxObjectName);

  SchedulerDetailsSetter.setDetails(tp, schedName, schedInstId);

  rsrcs.setThreadExecutor(threadExecutor);
  threadExecutor.initialize();

  rsrcs.setThreadPool(tp);
  if(tp instanceof SimpleThreadPool) {
    if(threadsInheritInitalizersClassLoader)
    ((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader);
  }
  tp.initialize();
  tpInited = true;

  rsrcs.setJobStore(js);

  // add plugins
  for (int i = 0; i < plugins.length; i++) {
    rsrcs.addSchedulerPlugin(plugins[i]);
  }

  //创建QuartzScheduler对象,重点关注,此为Quartz核心部分
  qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
  qsInited = true;

  // 创建Scheduler对象,QuartzScheduler并未直接实现Scheduler接口,而是作为了Scheduler的委托者
  Scheduler scheduler = instantiate(rsrcs, qs);
  ...
}

protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) {
  Scheduler scheduler = new StdScheduler(qs);
  return scheduler;
}

以上代码是经过大刀阔斧砍掉过的,原代码十分长,通篇下来主要是根据配置去创建一系列的对象,所有的对象最终都将被以上代码中的 **QuartzSchedulerResources** 对象所持有,这些对象共同协作才能最终组装出Quartz这台"机器", 通过以上代码也可大致窥探出创建了哪些对象实例,这些对象实例的创建大多都可通过quartz.properties进行配置。

其中,我们更应该关注的是 **QuartzScheduler** 对象的创建,它实则为Quartz的心脏。

QuartzScheduler

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval) throws SchedulerException {
  this.resources = resources;
  if (resources.getJobStore() instanceof JobListener) {
  addInternalJobListener((JobListener)resources.getJobStore());
  }

  //创建QuartzSchedulerThread对象,重点关注,此线程负责任务调度
  this.schedThread = new QuartzSchedulerThread(this, resources);
  ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
  //DefaultThreadExecutor对象,该方法的作用是启动schedThread线程
  schedThreadExecutor.execute(this.schedThread);
  if (idleWaitTime > 0) {
    this.schedThread.setIdleWaitTime(idleWaitTime);
  }

  jobMgr = new ExecutingJobsManager();
  addInternalJobListener(jobMgr);
  errLogger = new ErrorLogger();
  addInternalSchedulerListener(errLogger);

  signaler = new SchedulerSignalerImpl(this, this.schedThread);

  getLog().info("Quartz Scheduler v." + getVersion() + " created.");
}

以上代码,主要是创建了QuartzSchedulerThread对象,然后通过DefaultThreadExecutor进行启动。

QuartzSchedulerThread

QuartzSchedulerThread实现自Thread,我们接下来就看看其核心代码。

@Override
public void run() {
  int acquiresFailed = 0;

  //是否结束循环\调度
  while (!halted.get()) {
    try {
      synchronized (sigLock) {
      //如果是暂停状态,则在此阻塞,直到外部更改状态
        while (paused && !halted.get()) {
          try {
            sigLock.wait(1000L);
          } catch (InterruptedException ignore) {
          }
          acquiresFailed = 0;
        }

        if (halted.get()) {
          break;
        }
      }

      ......

      //获取可用线程数量 
      int availThreadCount =qsRsrcs.getThreadPool().blockForAvailableThreads();
      if(availThreadCount > 0) { 
        List<OperableTrigger> triggers;
        long now = System.currentTimeMillis();
        clearSignaledSchedulingChange();
      //从DB中取出一批即将要执行的Trigger(触发器), DB中该数据状态也会同步进行修改
        try {
          triggers = qsRsrcs.getJobStore().acquireNextTriggers(
          now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
          acquiresFailed = 0;
          if (log.isDebugEnabled())
          log.debug("batch acquisition of " + (triggers == null ? 0 : triggers.size()) + " triggers");
        } catch (JobPersistenceException jpe) {
          if (acquiresFailed == 0) {
            qs.notifySchedulerListenersError("An error occurred while scanning for the next triggers to fire.",jpe);
          }
          if (acquiresFailed < Integer.MAX_VALUE)
          acquiresFailed++;
          continue;
        } catch (RuntimeException e) {
          if (acquiresFailed == 0) {
            getLog().error("quartzSchedulerThreadLoop: RuntimeException " +e.getMessage(), e);
          }
          if (acquiresFailed < Integer.MAX_VALUE)
          acquiresFailed++;
          continue;
        }

        if (triggers != null && !triggers.isEmpty()) {

          ......


          List<TriggerFiredResult> bndles = new ArrayList<TriggerFiredResult>();

          boolean goAhead = true;
          synchronized(sigLock) {
            goAhead = !halted.get();
          }
          if(goAhead) {
            //取出触发器对应的任务,同步修改相关DB中的记录状态,并调整下次执行时间
            try {
              List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
              if(res != null)
                bndles = res;
            } catch (SchedulerException se) {
              qs.notifySchedulerListenersError("An error occurred while firing triggers '"+ triggers + "'", se);
              for (int i = 0; i < triggers.size(); i++) {
                qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
              }
              continue;
            }

          }

          //真正执行的方法,包装为JobRunShell, 并从线程池中获取线程进行执行
          for (int i = 0; i < bndles.size(); i++) {
            TriggerFiredResult result = bndles.get(i);
            TriggerFiredBundle bndle = result.getTriggerFiredBundle();
            Exception exception = result.getException();

            if (exception instanceof RuntimeException) {
              getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
              qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
              continue;
            }

            if (bndle == null) {
              qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
              continue;
            }

            JobRunShell shell = null;
            try {
              shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
              shell.initialize(qs);
            } catch (SchedulerException se) {
              qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
              continue;
            }

            if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
              getLog().error("ThreadPool.runInThread() return false!");
              qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
            }

          }
          continue; // while (!halted)
        }
      } else {
        continue; // while (!halted)
    }

    ......
}

以上代码同样进行过精简,该方法为quartz的核心调度流程。由于内部业务较为复杂,只在代码上加了简单的注释,不过主要流程就是 从DB中获取Trigger触发器和Job(任务), 同时通过更新DB数据状态来防止集群下的“争抢”,通过线程的wait和notify机制来协同线程调度,最终从线程池中获取线程来执行我们的任务。

ok, 到此,quartz这颗小心脏就已经跳动起来了。

那么,到此结束?

No, 一切才刚刚开始! 说好的xxl-job呢?

XxlJobDynamicScheduler

回到XxlJobDynamicSchedulerConfig,我们发现在初始化XxlJobDynamicScheduler对象后,会调用其start方法。那么,我们进入其start方法一探究竟。

public void start() throws Exception {
  // valid
  Assert.notNull(scheduler, "quartz scheduler is null");

  // 国际化
  initI18n();

  // 启动维护执行器注册信息守护线程
  JobRegistryMonitorHelper.getInstance().start();

  // 启动执行失败的任务扫描守护线程
  JobFailMonitorHelper.getInstance().start();

  // 初始化RPC (接收执行器注册和回调等), 在分析执行器的时候再来看,本次不看
  initRpcProvider();

  logger.info(">>>>>>>>> init xxl-job admin success.");
}

当执行器自动注册后,调度中心是如何去维护它的呢? 答案就在 JobRegistryMonitorHelper 线程里。

JobRegistryMonitorHelper

public void start(){
  registryThread = new Thread(new Runnable() {
    @Override
    public void run() {
      while (!toStop) {
        try {
          // 从XXL_JOB_QRTZ_TRIGGER_GROUP表中获取自动注册类型的执行器
          List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
          if (groupList!=null && !groupList.isEmpty()) {
            //注册信息记录在XXL_JOB_QRTZ_TRIGGER_REGISTRY表,删除90秒没心跳机器
            XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(RegistryConfig.DEAD_TIMEOUT);

            HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
            //XXL_JOB_QRTZ_TRIGGER_REGISTRY表获取存活的机器
            List<XxlJobRegistry> list =XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT);
            //appname 相同的形成集群
            if (list != null) {
              for (XxlJobRegistry item: list) {
                if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
                  String appName = item.getRegistryKey();
                  List<String> registryList = appAddressMap.get(appName);
                  if (registryList == null) {
                    registryList = new ArrayList<String>();
                  }

                  if (!registryList.contains(item.getRegistryValue())) {
                    registryList.add(item.getRegistryValue());
                  }
                  appAddressMap.put(appName, registryList);
                }
              }
            }

            // 维护集群地址(XXL_JOB_QRTZ_TRIGGER_GROUP表,地址逗号分隔)
            for (XxlJobGroup group: groupList) {
              List<String> registryList = appAddressMap.get(group.getAppName());
              String addressListStr = null;
                if (registryList!=null && !registryList.isEmpty()) {
                  Collections.sort(registryList);  
                  addressListStr = "";
                  for (String item:registryList) {
                    addressListStr += item + ",";
                  }                  
                  addressListStr = addressListStr.substring(0, addressListStr.length()-1);
                }
                group.setAddressList(addressListStr);
                XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
              }
            }
          } catch (Exception e) {
            ......
          }
          try {
            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
          } catch (InterruptedException e) {
            ......
          }
        }
        logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
      }
    });
    registryThread.setDaemon(true);
    registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
    registryThread.start();
  }

关于注册信息的维护比较简单,就是定时检查有没心跳,心跳体现在DB中(通过每次更新DB记录时间,来表示存活)。一定时间窗口内(默认90秒)没更新心跳的,就认为已经dead, 直接剔除,然后维护当前存活机器的地址。

JobFailMonitorHelper

当任务执行失败时,我们需要收到邮件报警。甚至有时候我们需要任务进行自动重试,那么,xxl-job是如何实现的呢? 答案就在 JobFailMonitorHelper 中。

public void start(){
monitorThread = new Thread(new Runnable() {

@Override
public void run() {

// monitor
while (!toStop) {
try {
//XXL_JOB_QRTZ_TRIGGER_LOG表中记录的是任务执行
//从XXL_JOB_QRTZ_TRIGGER_LOG表中取出执行失败的记录
List<Integer> failLogIds = XxlJobAdminConfig.getAdminConfig()
.getXxlJobLogDao().findFailJobLogIds(1000);
if (failLogIds!=null && !failLogIds.isEmpty()) {
for (int failLogId: failLogIds) {

//锁定日志记录
int lockRet = XxlJobAdminConfig.getAdminConfig()
.getXxlJobLogDao()
.updateAlarmStatus(failLogId, 0, -1);
if (lockRet < 1) {
continue;
}
XxlJobLog log = XxlJobAdminConfig.getAdminConfig()
.getXxlJobLogDao().load(failLogId);
//XXL_JOB_QRTZ_TRIGGER_INFO表中获取任务详情
XxlJobInfo info = XxlJobAdminConfig.getAdminConfig()
.getXxlJobInfoDao().loadById(log.getJobId());

// 没达到最大重试次数,则进行重试,日志中记录的就是剩余的重试次数
if (log.getExecutorFailRetryCount() > 0) {
//发起重试(触发流程参考后面章节)
JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), null);

.......

//更新日志 
XxlJobAdminConfig.getAdminConfig()
.getXxlJobLogDao().updateTriggerInfo(log);
}

// 失败任务报警
// 0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
int newAlarmStatus = 0;    
if (info!=null && info.getAlarmEmail()!=null
&& info.getAlarmEmail().trim().length()>0) {
boolean alarmResult = true;
try {
alarmResult = failAlarm(info, log);
} catch (Exception e) {
alarmResult = false;
logger.error(e.getMessage(), e);
}
newAlarmStatus = alarmResult?2:3;
} else {
newAlarmStatus = 1;
}
//更新报警状态
XxlJobAdminConfig.getAdminConfig()
.getXxlJobLogDao()
.updateAlarmStatus(failLogId, -1, newAlarmStatus);
}
}

TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
......
}
}

logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");

}
});
monitorThread.setDaemon(true);
monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
monitorThread.start();
}

至此,调度中心我们关心的主要流程就已经初始化完毕。现在,我们大致清楚了xxl-job初始化流程,调度中心对于我们而言,其核心功能无非对任务进行增删改查的管理以及触发和停止,增删改查还好,其实质就是对于DB的CRUD操作,但是触发调度和停止任务是怎么做的呢? 由于xxl-job是调度中心和执行器分离的,所以,上述问题换句话来说就是两者间是如何通信的。

答案就是RPC, 接下来,我们通过调度一个任务,来看看其执行流程。

执行流程

打开调度中心页面,在任务操作栏点击 **“启动”** 按钮,会发现其请求路径为 **“/jobinfo/start”**, 都到这一步了,学WEB是不是该秒懂,马上前往 /jobinfo/start。

@Controller
@RequestMapping("/jobinfo")
public class JobInfoController {
......

@RequestMapping("/start")
@ResponseBody
public ReturnT<String> start(int id) {
return xxlJobService.start(id);
}

......
}
@Service
public class XxlJobServiceImpl implements XxlJobService {
......

@Override
public ReturnT<String> start(int id) {
//XXL_JOB_QRTZ_TRIGGER_INFO表获取任务信息
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
String name = String.valueOf(xxlJobInfo.getId());
//获取cron表达式
String cronExpression = xxlJobInfo.getJobCron();

try {
boolean ret = XxlJobDynamicScheduler.addJob(name, cronExpression);
return ret?ReturnT.SUCCESS:ReturnT.FAIL;
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
return ReturnT.FAIL;
}
}
}
public class XxlJobDynamicScheduler {
public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
......

CronTrigger cronTrigger = TriggerBuilder.newTrigger().
withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();

// 任务最终将转换为RemoteHttpJobBean
Class<? extends Job> jobClass_ = RemoteHttpJobBean.class; 
JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();

// 通过quartz的scheduler (StdScheduler)调度任务
Date date = scheduler.scheduleJob(jobDetail, cronTrigger);

return true;
}
}
public class StdScheduler {

...
private QuartzScheduler sched;
...

public Date scheduleJob(JobDetail jobDetail, Trigger trigger)
throws SchedulerException {
//来到了我们之前说的quartz的心脏部分QuartzScheduler
return sched.scheduleJob(jobDetail, trigger);
}
}
public class QuartzScheduler implements RemotableQuartzScheduler {

......
private SchedulerSignaler signaler;
......

public Date scheduleJob(JobDetail jobDetail,
Trigger trigger) throws SchedulerException {

......
//唤醒线程
notifySchedulerThread(trigger.getNextFireTime().getTime());
......

return ft;
}

protected void notifySchedulerThread(long candidateNewNextFireTime) {
if (isSignalOnSchedulingChange()) {
//通过SchedulerSignalerImpl会调用到signalSchedulingChange方法
//SchedulerSignalerImpl.schedThread.signalSchedulingChange(candidateNewNextFireTime);
signaler.signalSchedulingChange(candidateNewNextFireTime);
}
}


public void signalSchedulingChange(long candidateNewNextFireTime) {
synchronized(sigLock) {
signaled = true;
signaledNextFireTime = candidateNewNextFireTime;
//唤醒线程
sigLock.notifyAll();
}
}
}

至此,一切又回到了我们之前介绍过的 **QuartzScheduler**。刚刚,提到我们的任务类型最终会被注册为**RemoteHttpJobBean**,这发生在哪一步? 其实就发生在 **JobRunShell **(之前提到所有任务都会被包装为JobRunShell 对象,然后在线程池中获取线程执行)中的initialize方法。

public class JobRunShell extends SchedulerListenerSupport implements Runnable {
......

public void initialize(QuartzScheduler sched)
throws SchedulerException {
this.qs = sched;

Job job = null;
JobDetail jobDetail = firedTriggerBundle.getJobDetail();

try {
//最终通过jobdetail的jobClass创建实例, 
//这个jobClass正是我们上面设置的 RemoteHttpJobBean
job = sched.getJobFactory().newJob(firedTriggerBundle, scheduler);
} catch (SchedulerException se) {
sched.notifySchedulerListenersError(
"An error occured instantiating job to be executed. job= '"
+ jobDetail.getKey() + "'", se);
throw se;
} catch (Throwable ncdfe) { // such as NoClassDefFoundError
SchedulerException se = new SchedulerException(
"Problem instantiating class '"
+ jobDetail.getJobClass().getName() + "' - ", ncdfe);
sched.notifySchedulerListenersError(
"An error occured instantiating job to be executed. job= '"
+ jobDetail.getKey() + "'", se);
throw se;
}

this.jec = new JobExecutionContextImpl(scheduler, firedTriggerBundle, job);
}

......

//启动
public void run() {
qs.addInternalSchedulerListener(this);

try {
OperableTrigger trigger = (OperableTrigger) jec.getTrigger();
JobDetail jobDetail = jec.getJobDetail();

do {

JobExecutionException jobExEx = null;
Job job = jec.getJobInstance();

......
try {
log.debug("Calling execute on job " + jobDetail.getKey());
//执行任务,调用RemoteHttpJobBean的executeInternal方法
job.execute(jec);
endTime = System.currentTimeMillis();
} catch (JobExecutionException jee) {
......
} catch (Throwable e) {
......
}

jec.setJobRunTime(endTime - startTime);

......

qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode);
break;
} while (true);

} finally {
qs.removeInternalSchedulerListener(this);
}
}
}

以上,JobRunShell线程启动时,最终会调用RemoteHttpJobBean的executeInternal方法。

public class RemoteHttpJobBean extends QuartzJobBean {
private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);

@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {

// load jobId
JobKey jobKey = context.getTrigger().getJobKey();
Integer jobId = Integer.valueOf(jobKey.getName());

// 实际调用JobTriggerPoolHelper.addTrigger方法,看下面代码
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
}

}
public class JobTriggerPoolHelper {
......
private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();
......

public static void trigger(int jobId, TriggerTypeEnum triggerType, 
int failRetryCount, String executorShardingParam,
String executorParam) {
helper.addTrigger(jobId, triggerType, failRetryCount, 
executorShardingParam, executorParam);
}


public void addTrigger(final int jobId, final TriggerTypeEnum triggerType, 
final int failRetryCount, final String executorShardingParam, final String executorParam) {

// 根据任务执行时间进行了线程池隔离,分快慢两个线程池,默认为快线程池
ThreadPoolExecutor triggerPool_ = fastTriggerPool;
AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
//在一定窗口期内(默认1分钟)达到条件(时间大于500毫秒10次)则进入慢线程池
if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) {
triggerPool_ = slowTriggerPool;
}

// 通过线程池执行
triggerPool_.execute(new Runnable() {
@Override
public void run() {

long start = System.currentTimeMillis();

try {
// 重点关注,到此时才是真正触发执行
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount,
executorShardingParam, executorParam);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {

// 时间窗口为1分钟,超过就清空,进入下一个周期
long minTim_now = System.currentTimeMillis()/60000;
if (minTim != minTim_now) {
minTim = minTim_now;
jobTimeoutCountMap.clear();
}

// 每超过500毫秒就记录超时一次
long cost = System.currentTimeMillis()-start;
if (cost > 500) {
AtomicInteger timeoutCount = jobTimeoutCountMap.put(jobId, new AtomicInteger(1));
if (timeoutCount != null) {
timeoutCount.incrementAndGet();
}
}

}

}
});
}
}

以上代码,我们可以清楚看到xxl-job对于线程池隔离的处理规则,其实对于我们在设计同类问题的时候还是具有一定的参考价值。当然,本段代码最值得我们关注的还是其真正调用了XxlJobTrigger的trigger方法,这才是最终真正触发任务执行的。作了这么多准备,似乎好戏才真正开始。

public class XxlJobTrigger {
......

public static void trigger(int jobId, TriggerTypeEnum triggerType, 
int failRetryCount, String executorShardingParam, 
String executorParam) {
// XXL_JOB_QRTZ_TRIGGER_INFO表获取任务信息
XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig()
.getXxlJobInfoDao().loadById(jobId);
if (jobInfo == null) {
logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
return;
}
if (executorParam != null) {
jobInfo.setExecutorParam(executorParam);
}
//算出失败重试次数
int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount :
jobInfo.getExecutorFailRetryCount();

//XXL_JOB_QRTZ_TRIGGER_GROUP表获取执行器相关信息
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig()
.getXxlJobGroupDao().load(jobInfo.getJobGroup());

// 如果有分片,就进行分片处理
int[] shardingParam = null;
if (executorShardingParam!=null){
String[] shardingArr = executorShardingParam.split("/");
if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
shardingParam = new int[2];
//分片序号
shardingParam[0] = Integer.valueOf(shardingArr[0]);
//总分片数
shardingParam[1] = Integer.valueOf(shardingArr[1]);
}
}

if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==
ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
&& group.getRegistryList()!=null && !group.getRegistryList().isEmpty()
&& shardingParam==null) {
//如果是SHARDING_BROADCAST(分片广播策略),则对应所有执行器都将被触发
for (int i = 0; i < group.getRegistryList().size(); i++) {
//触发方法,重点关注,代码紧接
processTrigger(group, jobInfo, finalFailRetryCount, 
triggerType, i, group.getRegistryList().size());
}
} else {
if (shardingParam == null) {
shardingParam = new int[]{0, 1};
}
//只触发一次
processTrigger(group, jobInfo, finalFailRetryCount, 
triggerType, shardingParam[0], shardingParam[1]);
}

}

......

private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){

// 阻塞处理策略
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum
.match(jobInfo.getExecutorBlockStrategy(),
ExecutorBlockStrategyEnum.SERIAL_EXECUTION);
//路由策略
ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum
.match(jobInfo.getExecutorRouteStrategy(), null);
//分片参数
String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)
?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;

// 记录日志
XxlJobLog jobLog = new XxlJobLog();
......
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);

// 组装TriggerParam参数
TriggerParam triggerParam = new TriggerParam();
......

// 获取相应的执行器地址
String address = null;
ReturnT<String> routeAddressResult = null;
if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST 
== executorRouteStrategyEnum) {
//如果是分片广播,就根据当前分片序号,取出执行器地址
if (index < group.getRegistryList().size()) {
address = group.getRegistryList().get(index);
} else {
address = group.getRegistryList().get(0);
}
} else {
//根据路由策略获取相应执行器地址
//一些列路由策略继承自ExecutorRouter
routeAddressResult = executorRouteStrategyEnum.getRouter()
.route(triggerParam, group.getRegistryList());
if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
address = routeAddressResult.getContent();
}
}
} else {
routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
}

//执行
ReturnT<String> triggerResult = null;
if (address != null) {
//经过一系列组装参数,路由选址后,最终开始执行,该方法在下面,重点关注
triggerResult = runExecutor(triggerParam, address);
} else {
triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
}


......
//更新日志 
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);

logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}


/**
* 最终执行的地方
* @param triggerParam
* @param address
* @return
*/
public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
ReturnT<String> runResult = null;
try {
//此处获取的为代理对象(注意)
ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
//真正执行的为代理对象
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
......
}

......

return runResult;
}
}

到此,我们离真相只差最后一步了。上面获取ExecutorBiz对象,然后通过ExecutorBiz进行最终执行,特别需要注意的是获取到的ExecutorBiz是个代理对象。如果没打开XxlJobDynamicScheduler.getExecutorBiz进行查看,直接点run, 你会觉得你的打开方式没对。

那么,最后,我们就来通过这个代理对象解开最后谜题吧。

public final class XxlJobDynamicScheduler {
......
private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = 
new ConcurrentHashMap<String, ExecutorBiz>();

/**
* 获取ExecutorBiz代理对象
* @param address
* @return
* @throws Exception
*/
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
if (address==null || address.trim().length()==0) {
return null;
}

// 从缓存中获取
address = address.trim();
ExecutorBiz executorBiz = executorBizRepository.get(address);
if (executorBiz != null) {
return executorBiz;
}

// 创建获取代理对象(重点看getObject方法)
executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
NetEnum.NETTY_HTTP,
Serializer.SerializeEnum.HESSIAN.getSerializer(),
CallType.SYNC,
LoadBalance.ROUND,
ExecutorBiz.class,
null,
5000,
address,
XxlJobAdminConfig.getAdminConfig().getAccessToken(),
null,
null).getObject();

//设置缓存
executorBizRepository.put(address, executorBiz);
return executorBiz;
}
}

看看代理对象内部实现

public class XxlRpcReferenceBean {

......

//重点关注的方法, 被代理对象的run方法最终会到此对象的invoke 
public Object getObject() {
return Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), new Class[] { iface },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

......

// 组装RPC请求参数
XxlRpcRequest xxlRpcRequest = new XxlRpcRequest();
xxlRpcRequest.setRequestId(UUID.randomUUID().toString());
xxlRpcRequest.setCreateMillisTime(System.currentTimeMillis());
xxlRpcRequest.setAccessToken(accessToken);
xxlRpcRequest.setClassName(className);
xxlRpcRequest.setMethodName(methodName);
xxlRpcRequest.setParameterTypes(parameterTypes);
xxlRpcRequest.setParameters(parameters);


......
//最终都会通过此方法发起RPC
//此处的client为上面创建代理对象时传入的NetEnum.NETTY_HTTP
//即NettyHttpClient对象
//最终会通过netty来与执行器发起通信,细节不再继续追溯 
client.asyncSend(finalAddress, xxlRpcRequest);

......

}
});
}

}

到此,xxl-job调度中心的初始化和调度执行流程,我们大概都知道了。那么,当调度中心向执行器发起调度请求时,执行器又是怎么做的呢?

那就还得再从执行器的初始化说起。

 执行器

我们还是以spring boot版本的执行器为例。

初始化

首先会创建并初始化 XxlJobSpringExecutor实例,如下:

@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobSpringExecutor xxlJobExecutor() {

    logger.info(">>>>>>>>>>> xxl-job config init.");
    XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
    xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
    xxlJobSpringExecutor.setAppName(appName);
    xxlJobSpringExecutor.setIp(ip);
    xxlJobSpringExecutor.setPort(port);
    xxlJobSpringExecutor.setAccessToken(accessToken);
    xxlJobSpringExecutor.setLogPath(logPath);
    xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
    return xxlJobSpringExecutor;
}

在初始化完成后会调用 XxlJobSpringExecutor 的start方法。

public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware {

    @Override
    public void start() throws Exception {
        // JobHandler注解名与spring 托管的bean(我们的job)建立映射关系并缓存到Map
        initJobHandlerRepository(applicationContext);

        // 指定使用SpringGlueFactory, 不在我们本次探讨范围,暂时忽略
        GlueFactory.refreshInstance(1);
// 调用父类XxlJobExecutor的start方法 super.start(); } }

我们看看initJobHandlerRepository方法。

private void initJobHandlerRepository(ApplicationContext applicationContext){

    if (applicationContext == null) {
        return;
    }
// 获取带有@JobHandler修饰的bean Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class); if (serviceBeanMap!=null && serviceBeanMap.size()>0) { for (Object serviceBean : serviceBeanMap.values()) { if (serviceBean instanceof IJobHandler){ //获取@JobHandler值 String name = serviceBean.getClass().getAnnotation(JobHandler.class).value(); IJobHandler handler = (IJobHandler) serviceBean; if (loadJobHandler(name) != null) { throw new RuntimeException("xxl-job jobhandler naming conflicts."); } //缓存到Map. 建立映射关系 registJobHandler(name, handler); } } } } public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){ return jobHandlerRepository.put(name, jobHandler); }

接下来看父类**XxlJobExecutor** 的start方法。

public class XxlJobExecutor  {
    ......
    public void start() throws Exception {
        // 设置job的日志目录
        XxlJobFileAppender.initLogPath(logPath);
        // 初始化AdminBiz代理对象,该代理对象用于与调度中心进行RPC通信
        initAdminBizList(adminAddresses, accessToken);
        // 日志清理线程
        JobLogFileCleanThread.getInstance().start(logRetentionDays);
        // 回调线程(RPC回调到调度中心)
        TriggerCallbackThread.getInstance().start();
        //启动服务并向调度中心发起注册请求
        port = port>0?port: NetUtil.findAvailablePort(9999);
        ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
        initRpcProvider(ip, port, appName, accessToken);
    }
    ......
}

其中,我们重点关注initAdminBizList 和 initRpcProvider 两个方法。

......

private static List<AdminBiz> adminBizList;

private void initAdminBizList(String adminAddresses, String accessToken) throws Exception {
    serializer = Serializer.SerializeEnum.HESSIAN.getSerializer();
    //如果是有多个调度中心地址,则创建多个实例
    if (adminAddresses!=null && adminAddresses.trim().length()>0) {
        for (String address: adminAddresses.trim().split(",")) {
            if (address!=null && address.trim().length()>0) {         
                //http://调度中心地址/api
                String addressUrl = address.concat(AdminBiz.MAPPING);
                //创建代理对象,似曾相识?
                //这在我们讲调度中心的时候,已经讲过。
                AdminBiz adminBiz = (AdminBiz) new XxlRpcReferenceBean(
                    NetEnum.NETTY_HTTP,
                    serializer,
                    CallType.SYNC,
                    LoadBalance.ROUND,
                    AdminBiz.class,
                    null,
                    10000,
                    addressUrl,
                    accessToken,
                    null,
                    null
                ).getObject();

                if (adminBizList == null) {
                    adminBizList = new ArrayList<AdminBiz>();
                }                

                //代理对象加入缓存
                adminBizList.add(adminBiz);
            }
        }
    }
}
......

接下来,我们再看看 initRpcProvider 这个最关键的方法之一,其包含了服务的启动。

......

private XxlRpcProviderFactory xxlRpcProviderFactory = null;

private void initRpcProvider(String ip, int port, String appName, String accessToken) throws Exception {

    // 获取当前服务地址 (ip:port)
    String address = IpUtil.getIpPort(ip, port);
    //组装注册参数
    Map<String, String> serviceRegistryParam = new HashMap<String, String>();
    serviceRegistryParam.put("appName", appName);
    serviceRegistryParam.put("address", address);
    xxlRpcProviderFactory = new XxlRpcProviderFactory();

    //最需要注意的是
    //NetEnum.NETTY_HTTP指定使用NettyHttpServer作为我们的服务器
    //ExecutorServiceRegistry为我们的服务注册的执行器
    xxlRpcProviderFactory.initConfig(NetEnum.NETTY_HTTP, Serializer.SerializeEnum.HESSIAN.getSerializer(), ip, port, accessToken, ExecutorServiceRegistry.class, serviceRegistryParam);

    // add services
    xxlRpcProviderFactory.addService(ExecutorBiz.class.getName(), null, new ExecutorBizImpl());

    // 启动服务,并向调度中心发起注册请求
    xxlRpcProviderFactory.start();
}

......

接下来,我们直接看启动服务的方法。

public class XxlRpcProviderFactory {

    ......
    private Server server;
    private ServiceRegistry serviceRegistry;
    private String serviceAddress;

    public void start() throws Exception {
        // 本(执行器)服务的地址
        serviceAddress = IpUtil.getIpPort(this.ip, port);
        // 即上面指定的NettyHttpServer
        server = netType.serverClass.newInstance();
        // 启动后回调此方法
        server.setStartedCallback(new BaseCallback() {
            @Override
            public void run() throws Exception {
                if (serviceRegistryClass != null) {
                    //即上面指定的ExecutorServiceRegistry
                    serviceRegistry = serviceRegistryClass.newInstance();
                    // 向调度中心发起注册请求
                    serviceRegistry.start(serviceRegistryParam);
                    if (serviceData.size() > 0) {
                        serviceRegistry.registry(serviceData.keySet(), serviceAddress);
                    }
                }
            }
        });
        ......

        //启动
        server.start(this);
    }
    ......
}

以上,会启动NettyHttpServer服务, 通过设置启动回调来向调度中心发起注册请求。接下来,看看是怎么注册的。

@Override

public void start(Map<String, String> param) {

    //调用ExecutorRegistryThread对象的start方法

    ExecutorRegistryThread.getInstance()

        .start(param.get("appName"), param.get("address"));

}
public class ExecutorRegistryThread {

    private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
    private static ExecutorRegistryThread instance = new ExecutorRegistryThread();
    public static ExecutorRegistryThread getInstance(){
        return instance;
    }
    private Thread registryThread;
    private volatile boolean toStop = false;
    public void start(final String appName, final String address){
        ......
       registryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!toStop) {
                    try {
                        //注册参数
                        RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appName, address);
                        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                            try {
                                //真正发起注册的方法
                                //adminBiz对象即为我们上面的代理对象
                                //触发的实际为代理对象的invoke方法
                                ReturnT<String> registryResult = adminBiz.registry(registryParam);
                                if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                    registryResult = ReturnT.SUCCESS;
                                    logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                    break;
                                } else {
                                    logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                }
                            } catch (Exception e) {
                                logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);
                            }
                        }
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }

                    try {
                        if (!toStop) {
                            //默认每隔30S触发一次注册
                            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                        }
                    } catch (InterruptedException e) {
                        if (!toStop) {
                            logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());
                        }
                    }
                }

                //移除注册信息
                ........
        });
        registryThread.setDaemon(true);
        registryThread.setName("xxl-job, executor ExecutorRegistryThread");
        //启动线程
        registryThread.start();
    }

    ......

    public void toStop() {
        toStop = true;
        // interrupt and wait
        registryThread.interrupt();
        try {
            registryThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }
}

以上,通过启动ExecutorRegistryThread线程进行注册,最终发起rpc请求的仍然是我们之前(调度中心)介绍的代理对象实例,就不作过多描述,该线程默认情况下会每隔30s发送心跳到调度中心。

 以上即为主要初始化流程。那么,我们的执行中心到底是如何接收调度中心发起的调度请求的呢?

执行流程

在回到NettyHttpServer的启动流程。

public class NettyHttpServer extends Server  {
private Thread thread;
@Override
public void start(final XxlRpcProviderFactory xxlRpcProviderFactory) throws Exception { thread = new Thread(new Runnable() { @Override public void run() { ...... try { // start server ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new HttpObjectAggregator(5*1024*1024)); //重点关注 ch.pipeline().addLast(new NettyHttpServerHandler(xxlRpcProviderFactory, serverHandlerPool)); } }).childOption(ChannelOption.SO_KEEPALIVE, true); ...... } ...... } }); thread.setDaemon(true); thread.start(); } ...... }

以上值得注意的是,在server启动时,会初始化NettyHttpServerHandler实例,当请求到来时,会到NettyHttpServerHandler的channelRead0方法。

public class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private static final Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class);
    private XxlRpcProviderFactory xxlRpcProviderFactory;
    private ThreadPoolExecutor serverHandlerPool;

    public NettyHttpServerHandler(final XxlRpcProviderFactory xxlRpcProviderFactory, final ThreadPoolExecutor serverHandlerPool) {
        this.xxlRpcProviderFactory = xxlRpcProviderFactory;
        this.serverHandlerPool = serverHandlerPool;
    }
    //处理请求
    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        // request parse
        final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());
        final String uri = msg.uri();
        final boolean keepAlive = HttpUtil.isKeepAlive(msg);
        // 通过线程池异步执行
        serverHandlerPool.execute(new Runnable() {
            @Override
            public void run() {
                process(ctx, uri, requestBytes, keepAlive);
            }
        });
    }
private void process(ChannelHandlerContext ctx, String uri, byte[] requestBytes, boolean keepAlive){ String requestId = null; try { if ("/services".equals(uri)) { // services mapping // request StringBuffer stringBuffer = new StringBuffer("<ui>"); for (String serviceKey: xxlRpcProviderFactory.getServiceData().keySet()) { stringBuffer.append("<li>").append(serviceKey).append(": ").append(xxlRpcProviderFactory.getServiceData().get(serviceKey)).append("</li>"); } stringBuffer.append("</ui>"); // response serialize byte[] responseBytes = stringBuffer.toString().getBytes("UTF-8"); // response-write writeResponse(ctx, keepAlive, responseBytes); } else { // valid if (requestBytes.length == 0) { throw new XxlRpcException("xxl-rpc request data empty."); } // request deserialize XxlRpcRequest xxlRpcRequest = (XxlRpcRequest) xxlRpcProviderFactory.getSerializer().deserialize(requestBytes, XxlRpcRequest.class); requestId = xxlRpcRequest.getRequestId(); // 处理请求 XxlRpcResponse xxlRpcResponse = xxlRpcProviderFactory.invokeService(xxlRpcRequest); // response serialize byte[] responseBytes = xxlRpcProviderFactory.getSerializer().serialize(xxlRpcResponse); // response-write writeResponse(ctx, keepAlive, responseBytes); } } catch (Exception e) { ...... } } ...... }
public XxlRpcResponse invokeService(XxlRpcRequest xxlRpcRequest) {

    ......
    String serviceKey = makeServiceKey(xxlRpcRequest.getClassName(), xxlRpcRequest.getVersion());
    //取出ExecutorBizImpl实例
    Object serviceBean = serviceData.get(serviceKey);
    ......
    try {
        // 反射调用ExecutorBizImpl对象run方法
        Class<?> serviceClass = serviceBean.getClass();
        String methodName = xxlRpcRequest.getMethodName();
        Class<?>[] parameterTypes = xxlRpcRequest.getParameterTypes();
        Object[] parameters = xxlRpcRequest.getParameters();
        Method method = serviceClass.getMethod(methodName, parameterTypes);
        method.setAccessible(true);
        Object result = method.invoke(serviceBean, parameters);
        xxlRpcResponse.setResult(result);
    } catch (Throwable t) {
        // catch error
        logger.error("xxl-rpc provider invokeService error.", t);
        xxlRpcResponse.setErrorMsg(ThrowableUtil.toString(t));
    }
    return xxlRpcResponse;

}
public class ExecutorBizImpl implements ExecutorBiz {

    ......
    @Override
    public ReturnT<String> run(TriggerParam triggerParam) {

        // 缓存获取JobThread对象
        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
        IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
        String removeOldReason = null;

        GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
        if (GlueTypeEnum.BEAN == glueTypeEnum) {
            // 缓存中获取IJobHandler对象(即我们的业务job)
            // 之前通过扫描注解存入缓存
            IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
            // valid old jobThread
            if (jobThread!=null && jobHandler != newJobHandler) {
                // change handler, need kill old thread
                removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
                jobThread = null;
                jobHandler = null;
            }
            // valid handler
            if (jobHandler == null) {
                jobHandler = newJobHandler;
                if (jobHandler == null) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
                }
            }
        } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
            // valid old jobThread
            if (jobThread != null &&
                    !(jobThread.getHandler() instanceof GlueJobHandler
                        && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
                // change handler or gluesource updated, need kill old thread
                removeOldReason = "change job source or glue type, and terminate the old job thread.";
                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                try {
                    //从DB中获取源码,通过groovy进行加载并实例化
                    IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
                    jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
                }
            }
        } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
            // valid old jobThread
            if (jobThread != null &&
                    !(jobThread.getHandler() instanceof ScriptJobHandler
                            && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
                // change script or gluesource updated, need kill old thread
                removeOldReason = "change job source or glue type, and terminate the old job thread.";
                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                //读取脚本,写入文件,最终执行通过commons-exec
                jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
            }
        } else {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");
        }

        // 阻塞策略
        if (jobThread != null) {
            ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
            if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
                // 丢弃后续调度
                if (jobThread.isRunningOrHasQueue()) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
                }
            } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
                // 覆盖之前调度
                if (jobThread.isRunningOrHasQueue()) {
                    removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();

                    jobThread = null;
                }
            } else {
                // just queue trigger
            }
        }

        // 第一次执行或者是覆盖之前调度策略
        if (jobThread == null) {
            //开启线程,执行任务
            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
        }

        // 触发任务入队
        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
        return pushResult;
    }
}

至此,我们离真相只差最后一步,最后再看看XxlJobExecutor.registJobThread

......
private static ConcurrentHashMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();

public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){
    //新线程执行
    JobThread newJobThread = new JobThread(jobId, handler);
    //线程执行
    newJobThread.start();
    logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});
    //放入缓存
    JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);
    if (oldJobThread != null) {
        //旧任务线程停止,覆盖策略
        oldJobThread.toStop(removeOldReason);
        oldJobThread.interrupt();
    }
    return newJobThread;
}
......
public class JobThread extends Thread{

    private static Logger logger = LoggerFactory.getLogger(JobThread.class);
    private int jobId;
    private IJobHandler handler;
    private LinkedBlockingQueue<TriggerParam> triggerQueue;
    private Set<Integer> triggerLogIdSet;        // avoid repeat trigger for the same TRIGGER_LOG_ID
    private volatile boolean toStop = false;
    private String stopReason;
    private boolean running = false;    // if running job
    private int idleTimes = 0;            // idel times

public JobThread(int jobId, IJobHandler handler) { this.jobId = jobId; this.handler = handler; this.triggerQueue = new LinkedBlockingQueue<TriggerParam>(); this.triggerLogIdSet = Collections.synchronizedSet(new HashSet<Integer>()); } public IJobHandler getHandler() { return handler; } /** * trigger入队,执行的时候出队 * * @param triggerParam * @return */ public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) { // avoid repeat if (triggerLogIdSet.contains(triggerParam.getLogId())) { logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId()); return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId()); } triggerLogIdSet.add(triggerParam.getLogId()); triggerQueue.add(triggerParam); return ReturnT.SUCCESS; } /** * kill job thread * * @param stopReason */ public void toStop(String stopReason) { /** * Thread.interrupt只支持终止线程的阻塞状态(wait、join、sleep), * 在阻塞出抛出InterruptedException异常,但是并不会终止运行的线程本身; * 所以需要注意,此处彻底销毁本线程,需要通过共享变量方式; */ this.toStop = true; this.stopReason = stopReason; } /** * is running job * @return */ public boolean isRunningOrHasQueue() { return running || triggerQueue.size()>0; } @Override public void run() { // init try { handler.init(); } catch (Throwable e) { logger.error(e.getMessage(), e); } // execute while(!toStop){ running = false; idleTimes++; TriggerParam triggerParam = null; ReturnT<String> executeResult = null; try { //出队消费,3秒获取不到就返回null triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS); if (triggerParam!=null) { running = true; idleTimes = 0; triggerLogIdSet.remove(triggerParam.getLogId()); // 日志 "logPath/yyyy-MM-dd/9999.log" String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId()); XxlJobFileAppender.contextHolder.set(logFileName); //任务分片数据 ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal())); // execute XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + triggerParam.getExecutorParams()); if (triggerParam.getExecutorTimeout() > 0) { //有超时限制 Thread futureThread = null; try { final TriggerParam triggerParamTmp = triggerParam; FutureTask<ReturnT<String>> futureTask = new FutureTask<ReturnT<String>>(new Callable<ReturnT<String>>() { @Override public ReturnT<String> call() throws Exception { //执行业务job return handler.execute(triggerParamTmp.getExecutorParams()); } }); futureThread = new Thread(futureTask); futureThread.start(); //可能超时 executeResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS); } catch (TimeoutException e) { XxlJobLogger.log("<br>----------- xxl-job job execute timeout"); XxlJobLogger.log(e); executeResult = new ReturnT<String>(IJobHandler.FAIL_TIMEOUT.getCode(), "job execute timeout "); } finally { futureThread.interrupt(); } } else { // 无超时限制的,直接执行 executeResult = handler.execute(triggerParam.getExecutorParams()); } ......
// destroy try { handler.destroy(); } catch (Throwable e) { logger.error(e.getMessage(), e); } logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread()); } }

我们的业务基本,都是实现IJobHandler的excute方法,因此,最终就会到我们的业务方法。

到此,我们的xxl-job之旅就暂且告一段落。其实其中还有不少内容值得去深探,有兴趣的可以继续去看看。

  if (applicationContext == null) {

        return;    }
    // 获取带有@JobHandler修饰的bean    Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);
    if (serviceBeanMap!=null && serviceBeanMap.size()>0) {        for (Object serviceBean : serviceBeanMap.values()) {            if (serviceBean instanceof IJobHandler){                //获取@JobHandler值                String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();                IJobHandler handler = (IJobHandler) serviceBean;                if (loadJobHandler(name) != null) {                    throw new RuntimeException("xxl-job jobhandler naming conflicts.");                }                //缓存到Map. 建立映射关系                registJobHandler(name, handler);            }        }    }}
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){        return jobHandlerRepository.put(name, jobHandler);    }```




接下来看父类**XxlJobExecutor** 的start方法。
```javapublic class XxlJobExecutor  {    ......            public void start() throws Exception {
        // 设置job的日志目录        XxlJobFileAppender.initLogPath(logPath);
        // 初始化AdminBiz代理对象,该代理对象用于与调度中心进行RPC通信        initAdminBizList(adminAddresses, accessToken);

        // 日志清理线程        JobLogFileCleanThread.getInstance().start(logRetentionDays);
        // 回调线程(RPC回调到调度中心)        TriggerCallbackThread.getInstance().start();
        //启动服务并向调度中心发起注册请求        port = port>0?port: NetUtil.findAvailablePort(9999);        ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();        initRpcProvider(ip, port, appName, accessToken);    }        ......}```
其中,我们重点关注initAdminBizList 和 initRpcProvider 两个方法。
```java......private static List<AdminBiz> adminBizList;    private void initAdminBizList(String adminAddresses, String accessToken) throws Exception {    serializer = Serializer.SerializeEnum.HESSIAN.getSerializer();    //如果是有多个调度中心地址,则创建多个实例    if (adminAddresses!=null && adminAddresses.trim().length()>0) {        for (String address: adminAddresses.trim().split(",")) {            if (address!=null && address.trim().length()>0) {                //http://调度中心地址/api                String addressUrl = address.concat(AdminBiz.MAPPING);
                //创建代理对象,似曾相识?                //这在我们讲调度中心的时候,已经讲过。                AdminBiz adminBiz = (AdminBiz) new XxlRpcReferenceBean(                    NetEnum.NETTY_HTTP,                    serializer,                    CallType.SYNC,                    LoadBalance.ROUND,                    AdminBiz.class,                    null,                    10000,                    addressUrl,                    accessToken,                    null,                    null                ).getObject();
                if (adminBizList == null) {                    adminBizList = new ArrayList<AdminBiz>();                }                                //代理对象加入缓存                adminBizList.add(adminBiz);            }        }    }}......```
接下来,我们再看看 initRpcProvider 这个最关键的方法之一,其包含了服务的启动。
```java......    private XxlRpcProviderFactory xxlRpcProviderFactory = null;
private void initRpcProvider(String ip, int port, String appName, String accessToken) throws Exception {
    // 获取当前服务地址 (ip:port)    String address = IpUtil.getIpPort(ip, port);    //组装注册参数    Map<String, String> serviceRegistryParam = new HashMap<String, String>();    serviceRegistryParam.put("appName", appName);    serviceRegistryParam.put("address", address);
    xxlRpcProviderFactory = new XxlRpcProviderFactory();    //最需要注意的是    //NetEnum.NETTY_HTTP指定使用NettyHttpServer作为我们的服务器    //ExecutorServiceRegistry为我们的服务注册的执行器    xxlRpcProviderFactory.initConfig(NetEnum.NETTY_HTTP, Serializer.SerializeEnum.HESSIAN.getSerializer(), ip, port, accessToken, ExecutorServiceRegistry.class, serviceRegistryParam);
    // add services    xxlRpcProviderFactory.addService(ExecutorBiz.class.getName(), null, new ExecutorBizImpl());
    // 启动服务,并向调度中心发起注册请求    xxlRpcProviderFactory.start();
}......```
接下来,我们直接看启动服务的方法。
```javapublic class XxlRpcProviderFactory {    ......            private Server server;private ServiceRegistry serviceRegistry;private String serviceAddress;
public void start() throws Exception {// 本(执行器)服务的地址serviceAddress = IpUtil.getIpPort(this.ip, port);        // 即上面指定的NettyHttpServerserver = netType.serverClass.newInstance();        // 启动后回调此方法server.setStartedCallback(new BaseCallback() {@Overridepublic void run() throws Exception {if (serviceRegistryClass != null) {                    //即上面指定的ExecutorServiceRegistryserviceRegistry = serviceRegistryClass.newInstance();                    // 向调度中心发起注册请求serviceRegistry.start(serviceRegistryParam);if (serviceData.size() > 0) {serviceRegistry.registry(serviceData.keySet(), serviceAddress);}}}});        ......//启动        server.start(this);}        ......}```
以上,会启动NettyHttpServer服务, 通过设置启动回调来向调度中心发起注册请求。接下来,看看是怎么注册的。
```java@Overridepublic void start(Map<String, String> param) {    //调用ExecutorRegistryThread对象的start方法    ExecutorRegistryThread.getInstance()        .start(param.get("appName"), param.get("address"));}```
```javapublic class ExecutorRegistryThread {    private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
    private static ExecutorRegistryThread instance = new ExecutorRegistryThread();    public static ExecutorRegistryThread getInstance(){        return instance;    }
    private Thread registryThread;    private volatile boolean toStop = false;    public void start(final String appName, final String address){
        ......                    registryThread = new Thread(new Runnable() {            @Override            public void run() {
                              while (!toStop) {                    try {                        //注册参数                        RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appName, address);                        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {                            try {                                //真正发起注册的方法                                //adminBiz对象即为我们上面的代理对象                                //触发的实际为代理对象的invoke方法                                ReturnT<String> registryResult = adminBiz.registry(registryParam);                                if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {                                    registryResult = ReturnT.SUCCESS;                                    logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});                                    break;                                } else {                                    logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});                                }                            } catch (Exception e) {                                logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);                            }
                        }                    } catch (Exception e) {                        if (!toStop) {                            logger.error(e.getMessage(), e);                        }
                    }
                    try {                        if (!toStop) {                            //默认每隔30S触发一次注册                            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);                        }                    } catch (InterruptedException e) {                        if (!toStop) {                            logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());                        }                    }                }                                //移除注册信息                ........        });        registryThread.setDaemon(true);        registryThread.setName("xxl-job, executor ExecutorRegistryThread");        //启动线程        registryThread.start();    }
    ......                                        public void toStop() {        toStop = true;        // interrupt and wait        registryThread.interrupt();        try {            registryThread.join();        } catch (InterruptedException e) {            logger.error(e.getMessage(), e);        }    }}
```
以上,通过启动ExecutorRegistryThread线程进行注册,最终发起rpc请求的仍然是我们之前(调度中心)介绍的代理对象实例,就不作过多描述,该线程默认情况下会每隔30s发送心跳到调度中心。
 以上即为主要初始化流程。那么,我们的执行中心到底是如何接收调度中心发起的调度请求的呢?


### 执行流程
在回到NettyHttpServer的启动流程。
```javapublic class NettyHttpServer extends Server  {
    private Thread thread;
    @Override    public void start(final XxlRpcProviderFactory xxlRpcProviderFactory) throws Exception {
        thread = new Thread(new Runnable() {
            @Override            public void run() {
                ......
                try {                    // start server                    ServerBootstrap bootstrap = new ServerBootstrap();                    bootstrap.group(bossGroup, workerGroup)                            .channel(NioServerSocketChannel.class)                            .childHandler(new ChannelInitializer<SocketChannel>() {                                @Override                                public void initChannel(SocketChannel ch) throws Exception {                                    ch.pipeline().addLast(new HttpServerCodec());                                    ch.pipeline().addLast(new HttpObjectAggregator(5*1024*1024));    //重点关注                                    ch.pipeline().addLast(new NettyHttpServerHandler(xxlRpcProviderFactory, serverHandlerPool));                                }                            })                            .childOption(ChannelOption.SO_KEEPALIVE, true);
                    ......                                        }                 ......
            }
        });        thread.setDaemon(true);        thread.start();    }
    ......
}```
以上值得注意的是,在server启动时,会初始化NettyHttpServerHandler实例,当请求到来时,会到NettyHttpServerHandler的channelRead0方法。
```javapublic class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {    private static final Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class);

    private XxlRpcProviderFactory xxlRpcProviderFactory;    private ThreadPoolExecutor serverHandlerPool;
    public NettyHttpServerHandler(final XxlRpcProviderFactory xxlRpcProviderFactory, final ThreadPoolExecutor serverHandlerPool) {        this.xxlRpcProviderFactory = xxlRpcProviderFactory;        this.serverHandlerPool = serverHandlerPool;    }
    //处理请求    @Override    protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        // request parse        final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());        final String uri = msg.uri();        final boolean keepAlive = HttpUtil.isKeepAlive(msg);
        // 通过线程池异步执行        serverHandlerPool.execute(new Runnable() {            @Override            public void run() {                process(ctx, uri, requestBytes, keepAlive);            }        });    }
    private void process(ChannelHandlerContext ctx, String uri, byte[] requestBytes, boolean keepAlive){        String requestId = null;        try {            if ("/services".equals(uri)) {// services mapping
                // request                StringBuffer stringBuffer = new StringBuffer("<ui>");                for (String serviceKey: xxlRpcProviderFactory.getServiceData().keySet()) {                    stringBuffer.append("<li>").append(serviceKey).append(": ").append(xxlRpcProviderFactory.getServiceData().get(serviceKey)).append("</li>");                }                stringBuffer.append("</ui>");
                // response serialize                byte[] responseBytes = stringBuffer.toString().getBytes("UTF-8");
                // response-write                writeResponse(ctx, keepAlive, responseBytes);
            } else {
                // valid                if (requestBytes.length == 0) {                    throw new XxlRpcException("xxl-rpc request data empty.");                }
                // request deserialize                XxlRpcRequest xxlRpcRequest = (XxlRpcRequest) xxlRpcProviderFactory.getSerializer().deserialize(requestBytes, XxlRpcRequest.class);                requestId = xxlRpcRequest.getRequestId();
                // 处理请求                XxlRpcResponse xxlRpcResponse = xxlRpcProviderFactory.invokeService(xxlRpcRequest);
                // response serialize                byte[] responseBytes = xxlRpcProviderFactory.getSerializer().serialize(xxlRpcResponse);
                // response-write                writeResponse(ctx, keepAlive, responseBytes);            }        } catch (Exception e) {            ......        }
    }
    ......
}```
```javapublic XxlRpcResponse invokeService(XxlRpcRequest xxlRpcRequest) {
    ......
    String serviceKey = makeServiceKey(xxlRpcRequest.getClassName(),                                        xxlRpcRequest.getVersion());    //取出ExecutorBizImpl实例Object serviceBean = serviceData.get(serviceKey);    ......            try {        // 反射调用ExecutorBizImpl对象run方法        Class<?> serviceClass = serviceBean.getClass();        String methodName = xxlRpcRequest.getMethodName();        Class<?>[] parameterTypes = xxlRpcRequest.getParameterTypes();        Object[] parameters = xxlRpcRequest.getParameters();
        Method method = serviceClass.getMethod(methodName, parameterTypes);        method.setAccessible(true);        Object result = method.invoke(serviceBean, parameters);
        xxlRpcResponse.setResult(result);    } catch (Throwable t) {        // catch error        logger.error("xxl-rpc provider invokeService error.", t);        xxlRpcResponse.setErrorMsg(ThrowableUtil.toString(t));    }
    return xxlRpcResponse;}```


```javapublic class ExecutorBizImpl implements ExecutorBiz {    ......            @Override    public ReturnT<String> run(TriggerParam triggerParam) {        // 缓存获取JobThread对象        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());        IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;        String removeOldReason = null;                GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());        if (GlueTypeEnum.BEAN == glueTypeEnum) {
            // 缓存中获取IJobHandler对象(即我们的业务job)            // 之前通过扫描注解存入缓存            IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
            // valid old jobThread            if (jobThread!=null && jobHandler != newJobHandler) {                // change handler, need kill old thread                removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
                jobThread = null;                jobHandler = null;            }
            // valid handler            if (jobHandler == null) {                jobHandler = newJobHandler;                if (jobHandler == null) {                    return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");                }            }
        } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
            // valid old jobThread            if (jobThread != null &&                    !(jobThread.getHandler() instanceof GlueJobHandler                        && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {                // change handler or gluesource updated, need kill old thread                removeOldReason = "change job source or glue type, and terminate the old job thread.";
                jobThread = null;                jobHandler = null;            }
            // valid handler            if (jobHandler == null) {                try {                    //从DB中获取源码,通过groovy进行加载并实例化                    IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());                    jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());                } catch (Exception e) {                    logger.error(e.getMessage(), e);                    return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());                }            }        } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
            // valid old jobThread            if (jobThread != null &&                    !(jobThread.getHandler() instanceof ScriptJobHandler                            && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {                // change script or gluesource updated, need kill old thread                removeOldReason = "change job source or glue type, and terminate the old job thread.";
                jobThread = null;                jobHandler = null;            }
            // valid handler            if (jobHandler == null) {                //读取脚本,写入文件,最终执行通过commons-exec                jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));            }        } else {            return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");        }
        // 阻塞策略        if (jobThread != null) {            ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);            if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {                // 丢弃后续调度                if (jobThread.isRunningOrHasQueue()) {                    return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());                }            } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {                // 覆盖之前调度                if (jobThread.isRunningOrHasQueue()) {                    removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();
                    jobThread = null;                }            } else {                // just queue trigger            }        }
        // 第一次执行或者是覆盖之前调度策略        if (jobThread == null) {            //开启线程,执行任务            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);        }
        // 触发任务入队        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);        return pushResult;    }    }```
至此,我们离真相只差最后一步,最后再看看XxlJobExecutor.registJobThread
```java......    private static ConcurrentHashMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){    //新线程执行    JobThread newJobThread = new JobThread(jobId, handler);    //线程执行    newJobThread.start();    logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});
    //放入缓存    JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);    if (oldJobThread != null) {        //旧任务线程停止,覆盖策略        oldJobThread.toStop(removeOldReason);        oldJobThread.interrupt();    }
    return newJobThread;}......```


```javapublic class JobThread extends Thread{private static Logger logger = LoggerFactory.getLogger(JobThread.class);
private int jobId;private IJobHandler handler;private LinkedBlockingQueue<TriggerParam> triggerQueue;private Set<Integer> triggerLogIdSet;// avoid repeat trigger for the same TRIGGER_LOG_ID
private volatile boolean toStop = false;private String stopReason;
    private boolean running = false;    // if running jobprivate int idleTimes = 0;// idel times

public JobThread(int jobId, IJobHandler handler) {this.jobId = jobId;this.handler = handler;this.triggerQueue = new LinkedBlockingQueue<TriggerParam>();this.triggerLogIdSet = Collections.synchronizedSet(new HashSet<Integer>());}public IJobHandler getHandler() {return handler;}
    /**     * trigger入队,执行的时候出队     *     * @param triggerParam     * @return     */public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) {// avoid repeatif (triggerLogIdSet.contains(triggerParam.getLogId())) {logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId());return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId());}
triggerLogIdSet.add(triggerParam.getLogId());triggerQueue.add(triggerParam);        return ReturnT.SUCCESS;}
    /**     * kill job thread     *     * @param stopReason     */public void toStop(String stopReason) {/** * Thread.interrupt只支持终止线程的阻塞状态(wait、join、sleep), * 在阻塞出抛出InterruptedException异常,但是并不会终止运行的线程本身; * 所以需要注意,此处彻底销毁本线程,需要通过共享变量方式; */this.toStop = true;this.stopReason = stopReason;}
    /**     * is running job     * @return     */    public boolean isRunningOrHasQueue() {        return running || triggerQueue.size()>0;    }
    @Overridepublic void run() {
    // init    try {handler.init();} catch (Throwable e) {    logger.error(e.getMessage(), e);}
// executewhile(!toStop){running = false;idleTimes++;
            TriggerParam triggerParam = null;            ReturnT<String> executeResult = null;            try {//出队消费,3秒获取不到就返回nulltriggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);if (triggerParam!=null) {running = true;idleTimes = 0;triggerLogIdSet.remove(triggerParam.getLogId());
// 日志 "logPath/yyyy-MM-dd/9999.log"String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId());XxlJobFileAppender.contextHolder.set(logFileName);                    //任务分片数据ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()));
// executeXxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + triggerParam.getExecutorParams());
if (triggerParam.getExecutorTimeout() > 0) {//有超时限制Thread futureThread = null;try {final TriggerParam triggerParamTmp = triggerParam;FutureTask<ReturnT<String>> futureTask = new FutureTask<ReturnT<String>>(new Callable<ReturnT<String>>() {@Overridepublic ReturnT<String> call() throws Exception {                                    //执行业务jobreturn handler.execute(triggerParamTmp.getExecutorParams());}});futureThread = new Thread(futureTask);futureThread.start();//可能超时executeResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);} catch (TimeoutException e) {
XxlJobLogger.log("<br>----------- xxl-job job execute timeout");XxlJobLogger.log(e);
executeResult = new ReturnT<String>(IJobHandler.FAIL_TIMEOUT.getCode(), "job execute timeout ");} finally {futureThread.interrupt();}} else {// 无超时限制的,直接执行executeResult = handler.execute(triggerParam.getExecutorParams());}
......
// destroytry {handler.destroy();} catch (Throwable e) {logger.error(e.getMessage(), e);}
logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());}}```
我们的业务基本,都是实现IJobHandler的excute方法,因此,最终就会到我们的业务方法。


到此,我们的xxl-job之旅就暂且告一段落。其实其中还有不少内容值得去深探,有兴趣的可以继续去看看。
```java
```

猜你喜欢

转载自www.cnblogs.com/getwind/p/12103375.html
今日推荐