透过源码看xxl-job

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

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

调度中心

初始化

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

XxlJobDynamicSchedulerConfig

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

  1. @Configuration
  2.  
  3. public class XxlJobDynamicSchedulerConfig {
  4.   @Bean
  5.   public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
  6.     SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
  7.     schedulerFactory.setDataSource(dataSource);
  8.     schedulerFactory.setAutoStartup(true); // 自动启动
  9.     schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
  10.     schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
  11.     schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
  12.     schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
  13.     return schedulerFactory;
  14.   }
  15.  
  16.   @Bean(initMethod = "start", destroyMethod = "destroy")
  17.   public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
  18.     Scheduler scheduler = schedulerFactory.getScheduler();
  19.     XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
  20.     xxlJobDynamicScheduler.setScheduler(scheduler);
  21.     return xxlJobDynamicScheduler;
  22.   }
  23. }

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

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

SchedulerFactoryBean

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

  1. @Override
  2. public void afterPropertiesSet() throws Exception {
  3.   if (this.dataSource == null && this.nonTransactionalDataSource != null) {
  4.   this.dataSource = this.nonTransactionalDataSource;
  5.   }
  6.  
  7.   if (this.applicationContext != null && this.resourceLoader == null) {
  8.   this.resourceLoader = this.applicationContext;
  9.   }
  10.  
  11.   // 初始化scheduler
  12.   this.scheduler = prepareScheduler(prepareSchedulerFactory());
  13.   try {
  14.   registerListeners();
  15.   registerJobsAndTriggers();
  16.   } catch (Exception ex) {
  17.     try {
  18.     this.scheduler.shutdown(true);
  19.     } catch (Exception ex2) {
  20.       logger.debug("Scheduler shutdown exception after registration failure", ex2);
  21.     }
  22.     throw ex;
  23.   }
  24. }

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

  1. private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
  2. SchedulerFactory schedulerFactory = this.schedulerFactory;
  3. if (schedulerFactory == null) {
  4.   //默认为StdSchedulerFactory
  5.    schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
  6.   if (schedulerFactory instanceof StdSchedulerFactory) {
  7.   //解析处理配置
  8.     initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
  9.   } else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) {
  10.     throw new IllegalArgumentException("StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
  11.   }
  12. }
  13. return schedulerFactory;
  14. }

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

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

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

StdSchedulerFactory

  1. public Scheduler getScheduler() throws SchedulerException {
  2.   if (cfg == null) {
  3.     initialize();
  4.   }
  5.   SchedulerRepository schedRep = SchedulerRepository.getInstance();
  6.   Scheduler sched = schedRep.lookup(getSchedulerName());
  7.  
  8.   //如果存在该对象,则直接返回
  9.   if (sched != null) {
  10.     if (sched.isShutdown()) {
  11.       schedRep.remove(getSchedulerName());
  12.     } else {
  13.       return sched;
  14.     }
  15.   }
  16.  
  17.   //重点关注
  18.   sched = instantiate();
  19.   return sched;
  20. }

下面就重点看看instantiate方法。

  1. private Scheduler instantiate() throws SchedulerException {
  2. ....
  3.  
  4.   QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
  5.   rsrcs.setName(schedName);
  6.   rsrcs.setThreadName(threadName);
  7.   rsrcs.setInstanceId(schedInstId);
  8.   rsrcs.setJobRunShellFactory(jrsf);
  9.   rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon);
  10.   rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);
  11.   rsrcs.setBatchTimeWindow(batchTimeWindow);
  12.   rsrcs.setMaxBatchSize(maxBatchSize);
  13.   rsrcs.setInterruptJobsOnShutdown(interruptJobsOnShutdown);
  14.   rsrcs.setInterruptJobsOnShutdownWithWait(interruptJobsOnShutdownWithWait);
  15.   rsrcs.setJMXExport(jmxExport);
  16.   rsrcs.setJMXObjectName(jmxObjectName);
  17.  
  18.   SchedulerDetailsSetter.setDetails(tp, schedName, schedInstId);
  19.  
  20.   rsrcs.setThreadExecutor(threadExecutor);
  21.   threadExecutor.initialize();
  22.  
  23.   rsrcs.setThreadPool(tp);
  24.   if(tp instanceof SimpleThreadPool) {
  25.     if(threadsInheritInitalizersClassLoader)
  26.     ((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader);
  27.   }
  28.   tp.initialize();
  29.   tpInited = true;
  30.  
  31.   rsrcs.setJobStore(js);
  32.  
  33.   // add plugins
  34.   for (int i = 0; i < plugins.length; i++) {
  35.     rsrcs.addSchedulerPlugin(plugins[i]);
  36.   }
  37.  
  38.   //创建QuartzScheduler对象,重点关注,此为Quartz核心部分
  39.   qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
  40.   qsInited = true;
  41.  
  42.   // 创建Scheduler对象,QuartzScheduler并未直接实现Scheduler接口,而是作为了Scheduler的委托者
  43.   Scheduler scheduler = instantiate(rsrcs, qs);
  44.   ...
  45. }
  46.  
  47. protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) {
  48.   Scheduler scheduler = new StdScheduler(qs);
  49.   return scheduler;
  50. }

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

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

QuartzScheduler

  1. public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval) throws SchedulerException {
  2.   this.resources = resources;
  3.   if (resources.getJobStore() instanceof JobListener) {
  4.   addInternalJobListener((JobListener)resources.getJobStore());
  5.   }
  6.  
  7.   //创建QuartzSchedulerThread对象,重点关注,此线程负责任务调度
  8.   this.schedThread = new QuartzSchedulerThread(this, resources);
  9.   ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
  10.   //DefaultThreadExecutor对象,该方法的作用是启动schedThread线程
  11.   schedThreadExecutor.execute(this.schedThread);
  12.   if (idleWaitTime > 0) {
  13.     this.schedThread.setIdleWaitTime(idleWaitTime);
  14.   }
  15.  
  16.   jobMgr = new ExecutingJobsManager();
  17.   addInternalJobListener(jobMgr);
  18.   errLogger = new ErrorLogger();
  19.   addInternalSchedulerListener(errLogger);
  20.  
  21.   signaler = new SchedulerSignalerImpl(this, this.schedThread);
  22.  
  23.   getLog().info("Quartz Scheduler v." + getVersion() + " created.");
  24. }

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

QuartzSchedulerThread

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

  1. @Override
  2. public void run() {
  3.   int acquiresFailed = 0;
  4.  
  5.   //是否结束循环\调度
  6.   while (!halted.get()) {
  7.     try {
  8.       synchronized (sigLock) {
  9.       //如果是暂停状态,则在此阻塞,直到外部更改状态
  10.         while (paused && !halted.get()) {
  11.           try {
  12.             sigLock.wait(1000L);
  13.           } catch (InterruptedException ignore) {
  14.           }
  15.           acquiresFailed = 0;
  16.         }
  17.  
  18.         if (halted.get()) {
  19.           break;
  20.         }
  21.       }
  22.  
  23.       ......
  24.  
  25.       //获取可用线程数量
  26.       int availThreadCount =qsRsrcs.getThreadPool().blockForAvailableThreads();
  27.       if(availThreadCount > 0) {
  28.         List<OperableTrigger> triggers;
  29.         long now = System.currentTimeMillis();
  30.         clearSignaledSchedulingChange();
  31.       //从DB中取出一批即将要执行的Trigger(触发器), DB中该数据状态也会同步进行修改
  32.         try {
  33.           triggers = qsRsrcs.getJobStore().acquireNextTriggers(
  34.           now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
  35.           acquiresFailed = 0;
  36.           if (log.isDebugEnabled())
  37.           log.debug("batch acquisition of " + (triggers == null ? 0 : triggers.size()) + " triggers");
  38.         } catch (JobPersistenceException jpe) {
  39.           if (acquiresFailed == 0) {
  40.             qs.notifySchedulerListenersError("An error occurred while scanning for the next triggers to fire.",jpe);
  41.           }
  42.           if (acquiresFailed < Integer.MAX_VALUE)
  43.           acquiresFailed++;
  44.           continue;
  45.         } catch (RuntimeException e) {
  46.           if (acquiresFailed == 0) {
  47.             getLog().error("quartzSchedulerThreadLoop: RuntimeException " +e.getMessage(), e);
  48.           }
  49.           if (acquiresFailed < Integer.MAX_VALUE)
  50.           acquiresFailed++;
  51.           continue;
  52.         }
  53.  
  54.         if (triggers != null && !triggers.isEmpty()) {
  55.  
  56.           ......
  57.  
  58.           List<TriggerFiredResult> bndles = new ArrayList<TriggerFiredResult>();
  59.  
  60.           boolean goAhead = true;
  61.           synchronized(sigLock) {
  62.             goAhead = !halted.get();
  63.           }
  64.           if(goAhead) {
  65.             //取出触发器对应的任务,同步修改相关DB中的记录状态,并调整下次执行时间
  66.             try {
  67.               List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
  68.               if(res != null)
  69.                 bndles = res;
  70.             } catch (SchedulerException se) {
  71.               qs.notifySchedulerListenersError("An error occurred while firing triggers '"+ triggers + "'", se);
  72.               for (int i = 0; i < triggers.size(); i++) {
  73.                 qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
  74.               }
  75.               continue;
  76.             }
  77.  
  78.           }
  79.  
  80.           //真正执行的方法,包装为JobRunShell, 并从线程池中获取线程进行执行
  81.           for (int i = 0; i < bndles.size(); i++) {
  82.             TriggerFiredResult result = bndles.get(i);
  83.             TriggerFiredBundle bndle = result.getTriggerFiredBundle();
  84.             Exception exception = result.getException();
  85.  
  86.             if (exception instanceof RuntimeException) {
  87.               getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
  88.               qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
  89.               continue;
  90.             }
  91.  
  92.             if (bndle == null) {
  93.               qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
  94.               continue;
  95.             }
  96.  
  97.             JobRunShell shell = null;
  98.             try {
  99.               shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
  100.               shell.initialize(qs);
  101.             } catch (SchedulerException se) {
  102.               qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
  103.               continue;
  104.             }
  105.  
  106.             if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
  107.               getLog().error("ThreadPool.runInThread() return false!");
  108.               qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
  109.             }
  110.  
  111.           }
  112.           continue; // while (!halted)
  113.         }
  114.       } else {
  115.         continue; // while (!halted)
  116.     }
  117.  
  118.     ......
  119. }

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

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

那么,到此结束?

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

XxlJobDynamicScheduler

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

  1. public void start() throws Exception {
  2.   // valid
  3.   Assert.notNull(scheduler, "quartz scheduler is null");
  4.  
  5.   // 国际化
  6.   initI18n();
  7.  
  8.   // 启动维护执行器注册信息守护线程
  9.   JobRegistryMonitorHelper.getInstance().start();
  10.  
  11.   // 启动执行失败的任务扫描守护线程
  12.   JobFailMonitorHelper.getInstance().start();
  13.  
  14.   // 初始化RPC (接收执行器注册和回调等), 在分析执行器的时候再来看,本次不看
  15.   initRpcProvider();
  16.  
  17.   logger.info(">>>>>>>>> init xxl-job admin success.");
  18. }

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

JobRegistryMonitorHelper

  1. public void start(){
  2.   registryThread = new Thread(new Runnable() {
  3.     @Override
  4.     public void run() {
  5.       while (!toStop) {
  6.         try {
  7.           // 从XXL_JOB_QRTZ_TRIGGER_GROUP表中获取自动注册类型的执行器
  8.           List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
  9.           if (groupList!=null && !groupList.isEmpty()) {
  10.             //注册信息记录在XXL_JOB_QRTZ_TRIGGER_REGISTRY表,删除90秒没心跳机器
  11.             XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(RegistryConfig.DEAD_TIMEOUT);
  12.  
  13.             HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
  14.             //XXL_JOB_QRTZ_TRIGGER_REGISTRY表获取存活的机器
  15.             List<XxlJobRegistry> list =XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT);
  16.             //appname 相同的形成集群
  17.             if (list != null) {
  18.               for (XxlJobRegistry item: list) {
  19.                 if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
  20.                   String appName = item.getRegistryKey();
  21.                   List<String> registryList = appAddressMap.get(appName);
  22.                   if (registryList == null) {
  23.                     registryList = new ArrayList<String>();
  24.                   }
  25.  
  26.                   if (!registryList.contains(item.getRegistryValue())) {
  27.                     registryList.add(item.getRegistryValue());
  28.                   }
  29.                   appAddressMap.put(appName, registryList);
  30.                 }
  31.               }
  32.             }
  33.  
  34.             // 维护集群地址(XXL_JOB_QRTZ_TRIGGER_GROUP表,地址逗号分隔)
  35.             for (XxlJobGroup group: groupList) {
  36.               List<String> registryList = appAddressMap.get(group.getAppName());
  37.               String addressListStr = null;
  38.                 if (registryList!=null && !registryList.isEmpty()) {
  39.                   Collections.sort(registryList);  
  40.                   addressListStr = "";
  41.                   for (String item:registryList) {
  42.                     addressListStr += item + ",";
  43.                   }                  
  44.                   addressListStr = addressListStr.substring(0, addressListStr.length()-1);
  45.                 }
  46.                 group.setAddressList(addressListStr);
  47.                 XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
  48.               }
  49.             }
  50.           } catch (Exception e) {
  51.             ......
  52.           }
  53.           try {
  54.             TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
  55.           } catch (InterruptedException e) {
  56.             ......
  57.           }
  58.         }
  59.         logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
  60.       }
  61.     });
  62.     registryThread.setDaemon(true);
  63.     registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
  64.     registryThread.start();
  65.   }

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

JobFailMonitorHelper

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

  1. public void start(){
  2.   monitorThread = new Thread(new Runnable() {
  3.  
  4.   @Override
  5.   public void run() {
  6.  
  7.   // monitor
  8.     while (!toStop) {
  9.       try {
  10.         //XXL_JOB_QRTZ_TRIGGER_LOG表中记录的是任务执行
  11.         //从XXL_JOB_QRTZ_TRIGGER_LOG表中取出执行失败的记录
  12.         List<Integer> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
  13.         if (failLogIds!=null && !failLogIds.isEmpty()) {
  14.           for (int failLogId: failLogIds) {
  15.           //锁定日志记录
  16.           int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
  17.           if (lockRet < 1) {
  18.             continue;
  19.           }
  20.           XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
  21.           //XXL_JOB_QRTZ_TRIGGER_INFO表中获取任务详情
  22.           XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
  23.   
  24.           // 没达到最大重试次数,则进行重试,日志中记录的就是剩余的重试次数
  25.           if (log.getExecutorFailRetryCount() > 0) {
  26.           //发起重试(触发流程参考后面章节)
  27.             JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), null);
  28.             .......
  29.             //更新日志
  30.             XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
  31.           }
  32.  
  33.           // 失败任务报警
  34.           // 0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
  35.           int newAlarmStatus = 0;
  36.           if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
  37.             boolean alarmResult = true;
  38.             try {
  39.               alarmResult = failAlarm(info, log);
  40.             } catch (Exception e) {
  41.               alarmResult = false;
  42.               logger.error(e.getMessage(), e);
  43.             }
  44.             newAlarmStatus = alarmResult?2:3;
  45.           } else {
  46.             newAlarmStatus = 1;
  47.           }
  48.           //更新报警状态
  49.           XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
  50.         }
  51.       }
  52.  
  53.       TimeUnit.SECONDS.sleep(10);
  54.       } catch (Exception e) {
  55.         ......
  56.       }
  57.     }
  58.     logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");
  59.     }
  60.   });
  61.   monitorThread.setDaemon(true);
  62.   monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
  63.   monitorThread.start();
  64. }

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

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

执行流程

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

  1. @Controller
  2. @RequestMapping("/jobinfo")
  3. public class JobInfoController {
  4.   ......
  5.  
  6.   @RequestMapping("/start")
  7.   @ResponseBody
  8.   public ReturnT<String> start(int id) {
  9.     return xxlJobService.start(id);
  10.   }
  11.  
  12.   ......
  13. }
  1. @Service
  2. public class XxlJobServiceImpl implements XxlJobService {
  3.   ......
  4.  
  5.   @Override
  6.   public ReturnT<String> start(int id) {
  7.     //XXL_JOB_QRTZ_TRIGGER_INFO表获取任务信息
  8.     XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
  9.     String name = String.valueOf(xxlJobInfo.getId());
  10.     //获取cron表达式
  11.     String cronExpression = xxlJobInfo.getJobCron();
  12.  
  13.     try {
  14.       boolean ret = XxlJobDynamicScheduler.addJob(name, cronExpression);
  15.       return ret?ReturnT.SUCCESS:ReturnT.FAIL;
  16.     } catch (SchedulerException e) {
  17.       logger.error(e.getMessage(), e);
  18.       return ReturnT.FAIL;
  19.     }
  20.   }
  21. }
  1. public class XxlJobDynamicScheduler {
  2.   public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
  3.   ......
  4.  
  5.   CronTrigger cronTrigger = TriggerBuilder.newTrigger().
  6.   withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
  7.  
  8.   // 任务最终将转换为RemoteHttpJobBean
  9.   Class<? extends Job> jobClass_ = RemoteHttpJobBean.class;
  10.   JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
  11.  
  12.   // 通过quartz的scheduler (StdScheduler)调度任务
  13.   Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
  14.   
  15.   return true;
  16.   }
  17. }
  1. public class StdScheduler {
  2.  
  3.   ...
  4.   private QuartzScheduler sched;
  5.   ...
  6.  
  7.   public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
  8.   //来到了我们之前说的quartz的心脏部分QuartzScheduler
  9.     return sched.scheduleJob(jobDetail, trigger);
  10.   }
  11. }
  1. public class QuartzScheduler implements RemotableQuartzScheduler {
  2.  
  3.   ......
  4.   private SchedulerSignaler signaler;
  5.   ......
  6.  
  7.   public Date scheduleJob(JobDetail jobDetail,
  8.     Trigger trigger) throws SchedulerException {
  9.  
  10.     ......
  11.     //唤醒线程
  12.     notifySchedulerThread(trigger.getNextFireTime().getTime());
  13.     ......
  14.  
  15.     return ft;
  16.   }
  17.  
  18.   protected void notifySchedulerThread(long candidateNewNextFireTime) {
  19.     if (isSignalOnSchedulingChange()) {
  20.       //通过SchedulerSignalerImpl会调用到signalSchedulingChange方法
  21.       //SchedulerSignalerImpl.schedThread.signalSchedulingChange(candidateNewNextFireTime);
  22.       signaler.signalSchedulingChange(candidateNewNextFireTime);
  23.     }
  24.   }
  25.  
  26.   public void signalSchedulingChange(long candidateNewNextFireTime) {
  27.     synchronized(sigLock) {
  28.       signaled = true;
  29.       signaledNextFireTime = candidateNewNextFireTime;
  30.       //唤醒线程
  31.       sigLock.notifyAll();
  32.     }
  33.   }
  34. }

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

  1. public class JobRunShell extends SchedulerListenerSupport implements Runnable {
  2.   ......
  3.  
  4.   public void initialize(QuartzScheduler sched) throws SchedulerException {
  5.     this.qs = sched;
  6.  
  7.     Job job = null;
  8.     JobDetail jobDetail = firedTriggerBundle.getJobDetail();
  9.  
  10.     try {
  11.       //最终通过jobdetail的jobClass创建实例,
  12.       //这个jobClass正是我们上面设置的 RemoteHttpJobBean
  13.       job = sched.getJobFactory().newJob(firedTriggerBundle, scheduler);
  14.     } catch (SchedulerException se) {
  15.       sched.notifySchedulerListenersError("An error occured instantiating job to be executed. job= '"+ jobDetail.getKey() + "'", se);
  16.       throw se;
  17.     } catch (Throwable ncdfe) { // such as NoClassDefFoundError
  18.       SchedulerException se = new SchedulerException("Problem instantiating class '"+ jobDetail.getJobClass().getName() + "' - ", ncdfe);
  19.       sched.notifySchedulerListenersError("An error occured instantiating job to be executed. job= '"+ jobDetail.getKey() + "'", se);
  20.       throw se;
  21.     }
  22.  
  23.     this.jec = new JobExecutionContextImpl(scheduler, firedTriggerBundle, job);
  24.   }
  25.  
  26.   ......
  27.  
  28.   //启动
  29.   public void run() {
  30.     qs.addInternalSchedulerListener(this);
  31.  
  32.       try {
  33.         OperableTrigger trigger = (OperableTrigger) jec.getTrigger();
  34.         JobDetail jobDetail = jec.getJobDetail();
  35.       
  36.         do {
  37.  
  38.           JobExecutionException jobExEx = null;
  39.           Job job = jec.getJobInstance();
  40.  
  41.           ......
  42.           try {
  43.             log.debug("Calling execute on job " + jobDetail.getKey());
  44.             //执行任务,调用RemoteHttpJobBean的executeInternal方法
  45.             job.execute(jec);
  46.             endTime = System.currentTimeMillis();
  47.           } catch (JobExecutionException jee) {
  48.             ......
  49.           } catch (Throwable e) {
  50.             ......
  51.           }
  52.  
  53.           jec.setJobRunTime(endTime - startTime);
  54.  
  55.           ......
  56.  
  57.           qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode);
  58.           break;
  59.         } while (true);
  60.  
  61.       } finally {
  62.         qs.removeInternalSchedulerListener(this);
  63.       }
  64.     }
  65.   }

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

  1. public class RemoteHttpJobBean extends QuartzJobBean {
  2.   private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
  3.  
  4.   @Override
  5.   protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
  6.  
  7.     // load jobId
  8.     JobKey jobKey = context.getTrigger().getJobKey();
  9.     Integer jobId = Integer.valueOf(jobKey.getName());
  10.  
  11.     // 实际调用JobTriggerPoolHelper.addTrigger方法,看下面代码
  12.     JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
  13.   }
  14.  
  15. }
  1. public class JobTriggerPoolHelper {
  2.   ......
  3.   private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();
  4.   ......
  5.  
  6.   public static void trigger(int jobId, TriggerTypeEnum triggerType,int failRetryCount,String executorShardingParam,String executorParam) {
  7.     helper.addTrigger(jobId, triggerType, failRetryCount,
  8.     executorShardingParam, executorParam);
  9.   }
  10.  
  11.   public void addTrigger(final int jobId, final TriggerTypeEnum triggerType,final int failRetryCount, final String executorShardingParam, final String executorParam) {
  12.  
  13.     // 根据任务执行时间进行了线程池隔离,分快慢两个线程池,默认为快线程池
  14.     ThreadPoolExecutor triggerPool_ = fastTriggerPool;
  15.     AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
  16.     //在一定窗口期内(默认1分钟)达到条件(时间大于500毫秒10次)则进入慢线程池
  17.     if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) {
  18.       triggerPool_ = slowTriggerPool;
  19.     }
  20.  
  21.     // 通过线程池执行
  22.     triggerPool_.execute(new Runnable() {
  23.       @Override
  24.       public void run() {
  25.         long start = System.currentTimeMillis();
  26.         try {
  27.           // 重点关注,到此时才是真正触发执行
  28.           XxlJobTrigger.trigger(jobId, triggerType, failRetryCount,executorShardingParam, executorParam);
  29.         } catch (Exception e) {
  30.           logger.error(e.getMessage(), e);
  31.         } finally {
  32.  
  33.           // 时间窗口为1分钟,超过就清空,进入下一个周期
  34.           long minTim_now = System.currentTimeMillis()/60000;
  35.           if (minTim != minTim_now) {
  36.             minTim = minTim_now;
  37.             jobTimeoutCountMap.clear();
  38.           }
  39.  
  40.           // 每超过500毫秒就记录超时一次
  41.           long cost = System.currentTimeMillis()-start;
  42.           if (cost > 500) {
  43.             AtomicInteger timeoutCount = jobTimeoutCountMap.put(jobId, new AtomicInteger(1));
  44.             if (timeoutCount != null) {
  45.               timeoutCount.incrementAndGet();
  46.             }
  47.           }
  48.  
  49.         }
  50.  
  51.       }
  52.     });
  53.   }
  54. }

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

  1. public class XxlJobTrigger {
  2.   ......
  3.  
  4.   public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam) {
  5.   // XXL_JOB_QRTZ_TRIGGER_INFO表获取任务信息
  6.   XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
  7.   if (jobInfo == null) {
  8.     logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
  9.     return;
  10.   }
  11.   if (executorParam != null) {
  12.     jobInfo.setExecutorParam(executorParam);
  13.   }
  14.   //算出失败重试次数
  15.   int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount :
  16.   jobInfo.getExecutorFailRetryCount();
  17.  
  18.   //XXL_JOB_QRTZ_TRIGGER_GROUP表获取执行器相关信息
  19.   XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
  20.  
  21.   // 如果有分片,就进行分片处理
  22.   int[] shardingParam = null;
  23.   if (executorShardingParam!=null){
  24.     String[] shardingArr = executorShardingParam.split("/");
  25.       if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
  26.         shardingParam = new int[2];
  27.         //分片序号
  28.         shardingParam[0] = Integer.valueOf(shardingArr[0]);
  29.         //总分片数
  30.         shardingParam[1] = Integer.valueOf(shardingArr[1]);
  31.       }
  32.     }
  33.  
  34.     if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==
  35.       ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
  36.       && group.getRegistryList()!=null && !group.getRegistryList().isEmpty()
  37.       && shardingParam==null) {
  38.       //如果是SHARDING_BROADCAST(分片广播策略),则对应所有执行器都将被触发
  39.         for (int i = 0; i < group.getRegistryList().size(); i++) {
  40.           //触发方法,重点关注,代码紧接
  41.           processTrigger(group, jobInfo, finalFailRetryCount,
  42.           triggerType, i, group.getRegistryList().size());
  43.         }
  44.     } else {
  45.       if (shardingParam == null) {
  46.         shardingParam = new int[]{0, 1};
  47.     }
  48.     //只触发一次
  49.     processTrigger(group, jobInfo, finalFailRetryCount,
  50.     triggerType, shardingParam[0], shardingParam[1]);
  51.   }
  52.  
  53. }
  54.  
  55. ......
  56.  
  57. private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){
  58.   // 阻塞处理策略
  59.   ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(),ExecutorBlockStrategyEnum.SERIAL_EXECUTION);
  60.   //路由策略
  61.   ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);
  62.   //分片参数
  63.   String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;
  64.  
  65.   // 记录日志
  66.   XxlJobLog jobLog = new XxlJobLog();
  67.   ......
  68.   XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
  69.  
  70.   // 组装TriggerParam参数
  71.   TriggerParam triggerParam = new TriggerParam();
  72.   ......
  73.  
  74.   // 获取相应的执行器地址
  75.   String address = null;
  76.   ReturnT<String> routeAddressResult = null;
  77.     if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
  78.       if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
  79.         //如果是分片广播,就根据当前分片序号,取出执行器地址
  80.         if (index < group.getRegistryList().size()) {
  81.           address = group.getRegistryList().get(index);
  82.         } else {
  83.           address = group.getRegistryList().get(0);
  84.         }
  85.       } else {
  86.       //根据路由策略获取相应执行器地址
  87.       //一些列路由策略继承自ExecutorRouter
  88.       routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
  89.       if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
  90.         address = routeAddressResult.getContent();
  91.       }
  92.     }
  93.   } else {
  94.     routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
  95.   }
  96.  
  97.   //执行
  98.   ReturnT<String> triggerResult = null;
  99.   if (address != null) {
  100.     //经过一系列组装参数,路由选址后,最终开始执行,该方法在下面,重点关注
  101.     triggerResult = runExecutor(triggerParam, address);
  102.   } else {
  103.     triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
  104.   }
  105.   ......
  106.   //更新日志
  107.   XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);
  108.  
  109.   logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
  110. }
  111.     
  112.  
  113.   /**
  114.   * 最终执行的地方
  115.   * @param triggerParam
  116.   * @param address
  117.   * @return
  118.   */
  119.   public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
  120.   ReturnT<String> runResult = null;
  121.     try {
  122.       //此处获取的为代理对象(注意)
  123.       ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
  124.       //真正执行的为代理对象
  125.       runResult = executorBiz.run(triggerParam);
  126.     } catch (Exception e) {
  127.       ......
  128.     }
  129.  
  130.     ......
  131.  
  132.     return runResult;
  133.   }
  134. }

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

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

  1. public final class XxlJobDynamicScheduler {
  2.   ......
  3.   private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
  4.  
  5.   /**
  6.   * 获取ExecutorBiz代理对象
  7.   * @param address
  8.   * @return
  9.   * @throws Exception
  10.   */
  11.   public static ExecutorBiz getExecutorBiz(String address) throws Exception {
  12.   if (address==null || address.trim().length()==0) {
  13.     return null;
  14.   }
  15.  
  16.   // 从缓存中获取
  17.   address = address.trim();
  18.   ExecutorBiz executorBiz = executorBizRepository.get(address);
  19.   if (executorBiz != null) {
  20.     return executorBiz;
  21.   }
  22.  
  23.     // 创建获取代理对象(重点看getObject方法)
  24.     executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
  25.     NetEnum.NETTY_HTTP,
  26.     Serializer.SerializeEnum.HESSIAN.getSerializer(),
  27.     CallType.SYNC,
  28.     LoadBalance.ROUND,
  29.     ExecutorBiz.class,
  30.     null,
  31.     5000,
  32.     address,
  33.     XxlJobAdminConfig.getAdminConfig().getAccessToken(),
  34.     null,
  35.     null).getObject();
  36.  
  37.     //设置缓存
  38.     executorBizRepository.put(address, executorBiz);
  39.     return executorBiz;
  40.   }
  41. }

看看代理对象内部实现

  1. public class XxlRpcReferenceBean {
  2.  
  3.   ......
  4.  
  5.   //重点关注的方法, 被代理对象的run方法最终会到此对象的invoke
  6.   public Object getObject() {
  7.   return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { iface },
  8.     new InvocationHandler() {
  9.       @Override
  10.       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  11.         ......
  12.         // 组装RPC请求参数
  13.         XxlRpcRequest xxlRpcRequest = new XxlRpcRequest();
  14.         xxlRpcRequest.setRequestId(UUID.randomUUID().toString());
  15.         xxlRpcRequest.setCreateMillisTime(System.currentTimeMillis());
  16.         xxlRpcRequest.setAccessToken(accessToken);
  17.         xxlRpcRequest.setClassName(className);
  18.         xxlRpcRequest.setMethodName(methodName);
  19.         xxlRpcRequest.setParameterTypes(parameterTypes);
  20.         xxlRpcRequest.setParameters(parameters);
  21.     
  22.  
  23.         ......
  24.         //最终都会通过此方法发起RPC
  25.         //此处的client为上面创建代理对象时传入的NetEnum.NETTY_HTTP
  26.         //即NettyHttpClient对象
  27.         //最终会通过netty来与执行器发起通信,细节不再继续追溯
  28.         client.asyncSend(finalAddress, xxlRpcRequest);
  29.  
  30.         ......
  31.       }
  32.     });
  33.   }
  34. }

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

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

执行器

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

初始化

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

  1. @Bean(initMethod = "start", destroyMethod = "destroy")
  2. public XxlJobSpringExecutor xxlJobExecutor() {
  3.  
  4. logger.info(">>>>>>>>>>> xxl-job config init.");
  5. XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
  6. xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
  7. xxlJobSpringExecutor.setAppName(appName);
  8. xxlJobSpringExecutor.setIp(ip);
  9. xxlJobSpringExecutor.setPort(port);
  10. xxlJobSpringExecutor.setAccessToken(accessToken);
  11. xxlJobSpringExecutor.setLogPath(logPath);
  12. xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
  13. return xxlJobSpringExecutor;
  14. }

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

  1. public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware {
  2.  
  3. @Override
  4. public void start() throws Exception {
  5. // JobHandler注解名与spring 托管的bean(我们的job)建立映射关系并缓存到Map
  6. initJobHandlerRepository(applicationContext);
  7.  
  8. // 指定使用SpringGlueFactory, 不在我们本次探讨范围,暂时忽略
  9. GlueFactory.refreshInstance(1);
  10. // 调用父类XxlJobExecutor的start方法
  11. super.start();
  12. }
  13. }

我们看看initJobHandlerRepository方法。

  1. private void initJobHandlerRepository(ApplicationContext applicationContext){
  2.  
  3. if (applicationContext == null) {
  4. return;
  5. }
  6. // 获取带有@JobHandler修饰的bean
  7. Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);
  8. if (serviceBeanMap!=null && serviceBeanMap.size()>0) {
  9. for (Object serviceBean : serviceBeanMap.values()) {
  10. if (serviceBean instanceof IJobHandler){
  11. //获取@JobHandler值
  12. String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();
  13. IJobHandler handler = (IJobHandler) serviceBean;
  14. if (loadJobHandler(name) != null) {
  15. throw new RuntimeException("xxl-job jobhandler naming conflicts.");
  16. }
  17.  
  18. //缓存到Map. 建立映射关系
  19. registJobHandler(name, handler);
  20. }
  21. }
  22. }
  23. }
  24.  
  25. public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
  26. return jobHandlerRepository.put(name, jobHandler);
  27. }

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

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

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

  1. ......
  2.  
  3. private static List<AdminBiz> adminBizList;
  4.  
  5. private void initAdminBizList(String adminAddresses, String accessToken) throws Exception {
  6. serializer = Serializer.SerializeEnum.HESSIAN.getSerializer();
  7. //如果是有多个调度中心地址,则创建多个实例
  8. if (adminAddresses!=null && adminAddresses.trim().length()>0) {
  9. for (String address: adminAddresses.trim().split(",")) {
  10. if (address!=null && address.trim().length()>0) {
  11. //http://调度中心地址/api
  12. String addressUrl = address.concat(AdminBiz.MAPPING);
  13. //创建代理对象,似曾相识?
  14. //这在我们讲调度中心的时候,已经讲过。
  15. AdminBiz adminBiz = (AdminBiz) new XxlRpcReferenceBean(
  16. NetEnum.NETTY_HTTP,
  17. serializer,
  18. CallType.SYNC,
  19. LoadBalance.ROUND,
  20. AdminBiz.class,
  21. null,
  22. 10000,
  23. addressUrl,
  24. accessToken,
  25. null,
  26. null
  27. ).getObject();
  28.  
  29. if (adminBizList == null) {
  30. adminBizList = new ArrayList<AdminBiz>();
  31. }
  32.  
  33. //代理对象加入缓存
  34. adminBizList.add(adminBiz);
  35. }
  36. }
  37. }
  38. }
  39. ......

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

  1. ......
  2.  
  3. private XxlRpcProviderFactory xxlRpcProviderFactory = null;
  4.  
  5. private void initRpcProvider(String ip, int port, String appName, String accessToken) throws Exception {
  6.  
  7. // 获取当前服务地址 (ip:port)
  8. String address = IpUtil.getIpPort(ip, port);
  9. //组装注册参数
  10. Map<String, String> serviceRegistryParam = new HashMap<String, String>();
  11. serviceRegistryParam.put("appName", appName);
  12. serviceRegistryParam.put("address", address);
  13. xxlRpcProviderFactory = new XxlRpcProviderFactory();
  14.  
  15. //最需要注意的是
  16. //NetEnum.NETTY_HTTP指定使用NettyHttpServer作为我们的服务器
  17. //ExecutorServiceRegistry为我们的服务注册的执行器
  18. xxlRpcProviderFactory.initConfig(NetEnum.NETTY_HTTP, Serializer.SerializeEnum.HESSIAN.getSerializer(), ip, port, accessToken, ExecutorServiceRegistry.class, serviceRegistryParam);
  19.  
  20. // add services
  21. xxlRpcProviderFactory.addService(ExecutorBiz.class.getName(), null, new ExecutorBizImpl());
  22.  
  23. // 启动服务,并向调度中心发起注册请求
  24. xxlRpcProviderFactory.start();
  25. }
  26.  
  27. ......

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

  1. public class XxlRpcProviderFactory {
  2.  
  3. ......
  4. private Server server;
  5. private ServiceRegistry serviceRegistry;
  6. private String serviceAddress;
  7.  
  8. public void start() throws Exception {
  9. // 本(执行器)服务的地址
  10. serviceAddress = IpUtil.getIpPort(this.ip, port);
  11. // 即上面指定的NettyHttpServer
  12. server = netType.serverClass.newInstance();
  13. // 启动后回调此方法
  14. server.setStartedCallback(new BaseCallback() {
  15. @Override
  16. public void run() throws Exception {
  17. if (serviceRegistryClass != null) {
  18. //即上面指定的ExecutorServiceRegistry
  19. serviceRegistry = serviceRegistryClass.newInstance();
  20. // 向调度中心发起注册请求
  21. serviceRegistry.start(serviceRegistryParam);
  22. if (serviceData.size() > 0) {
  23. serviceRegistry.registry(serviceData.keySet(), serviceAddress);
  24. }
  25. }
  26. }
  27. });
  28. ......
  29.  
  30. //启动
  31. server.start(this);
  32. }
  33. ......
  34. }

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

  1. @Override
  2.  
  3. public void start(Map<String, String> param) {
  4.  
  5. //调用ExecutorRegistryThread对象的start方法
  6.  
  7. ExecutorRegistryThread.getInstance()
  8.  
  9. .start(param.get("appName"), param.get("address"));
  10.  
  11. }
  1. public class ExecutorRegistryThread {
  2.  
  3. private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
  4. private static ExecutorRegistryThread instance = new ExecutorRegistryThread();
  5. public static ExecutorRegistryThread getInstance(){
  6. return instance;
  7. }
  8. private Thread registryThread;
  9. private volatile boolean toStop = false;
  10. public void start(final String appName, final String address){
  11. ......
  12. registryThread = new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. while (!toStop) {
  16. try {
  17. //注册参数
  18. RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appName, address);
  19. for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
  20. try {
  21. //真正发起注册的方法
  22. //adminBiz对象即为我们上面的代理对象
  23. //触发的实际为代理对象的invoke方法
  24. ReturnT<String> registryResult = adminBiz.registry(registryParam);
  25. if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
  26. registryResult = ReturnT.SUCCESS;
  27. logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
  28. break;
  29. } else {
  30. logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
  31. }
  32. } catch (Exception e) {
  33. logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);
  34. }
  35. }
  36. } catch (Exception e) {
  37. if (!toStop) {
  38. logger.error(e.getMessage(), e);
  39. }
  40. }
  41.  
  42. try {
  43. if (!toStop) {
  44. //默认每隔30S触发一次注册
  45. TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
  46. }
  47. } catch (InterruptedException e) {
  48. if (!toStop) {
  49. logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());
  50. }
  51. }
  52. }
  53.  
  54. //移除注册信息
  55. ........
  56. });
  57. registryThread.setDaemon(true);
  58. registryThread.setName("xxl-job, executor ExecutorRegistryThread");
  59. //启动线程
  60. registryThread.start();
  61. }
  62.  
  63. ......
  64.  
  65. public void toStop() {
  66. toStop = true;
  67. // interrupt and wait
  68. registryThread.interrupt();
  69. try {
  70. registryThread.join();
  71. } catch (InterruptedException e) {
  72. logger.error(e.getMessage(), e);
  73. }
  74. }
  75. }

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

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

执行流程

在回到NettyHttpServer的启动流程。

  1. public class NettyHttpServer extends Server {
  2. private Thread thread;
  3. @Override
  4. public void start(final XxlRpcProviderFactory xxlRpcProviderFactory) throws Exception {
  5. thread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. ......
  9. try {
  10. // start server
  11. ServerBootstrap bootstrap = new ServerBootstrap();
  12. bootstrap.group(bossGroup, workerGroup)
  13. .channel(NioServerSocketChannel.class)
  14. .childHandler(new ChannelInitializer<SocketChannel>() {
  15. @Override
  16. public void initChannel(SocketChannel ch) throws Exception {
  17. ch.pipeline().addLast(new HttpServerCodec());
  18. ch.pipeline().addLast(new HttpObjectAggregator(5*1024*1024));
  19. //重点关注
  20. ch.pipeline().addLast(new NettyHttpServerHandler(xxlRpcProviderFactory, serverHandlerPool));
  21. }
  22. }).childOption(ChannelOption.SO_KEEPALIVE, true);
  23. ......
  24. }
  25. ......
  26. }
  27. });
  28. thread.setDaemon(true);
  29. thread.start();
  30. }
  31. ......
  32.  
  33. }

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

  1. public class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  2.  
  3. private static final Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class);
  4. private XxlRpcProviderFactory xxlRpcProviderFactory;
  5. private ThreadPoolExecutor serverHandlerPool;
  6.  
  7. public NettyHttpServerHandler(final XxlRpcProviderFactory xxlRpcProviderFactory, final ThreadPoolExecutor serverHandlerPool) {
  8. this.xxlRpcProviderFactory = xxlRpcProviderFactory;
  9. this.serverHandlerPool = serverHandlerPool;
  10. }
  11. //处理请求
  12. @Override
  13. protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
  14. // request parse
  15. final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());
  16. final String uri = msg.uri();
  17. final boolean keepAlive = HttpUtil.isKeepAlive(msg);
  18. // 通过线程池异步执行
  19. serverHandlerPool.execute(new Runnable() {
  20. @Override
  21. public void run() {
  22. process(ctx, uri, requestBytes, keepAlive);
  23. }
  24. });
  25. }
  26. private void process(ChannelHandlerContext ctx, String uri, byte[] requestBytes, boolean keepAlive){
  27. String requestId = null;
  28. try {
  29. if ("/services".equals(uri)) { // services mapping
  30. // request
  31. StringBuffer stringBuffer = new StringBuffer("<ui>");
  32. for (String serviceKey: xxlRpcProviderFactory.getServiceData().keySet()) {
  33. stringBuffer.append("<li>").append(serviceKey).append(": ").append(xxlRpcProviderFactory.getServiceData().get(serviceKey)).append("</li>");
  34. }
  35. stringBuffer.append("</ui>");
  36. // response serialize
  37. byte[] responseBytes = stringBuffer.toString().getBytes("UTF-8");
  38. // response-write
  39. writeResponse(ctx, keepAlive, responseBytes);
  40. } else {
  41. // valid
  42. if (requestBytes.length == 0) {
  43. throw new XxlRpcException("xxl-rpc request data empty.");
  44. }
  45. // request deserialize
  46. XxlRpcRequest xxlRpcRequest = (XxlRpcRequest) xxlRpcProviderFactory.getSerializer().deserialize(requestBytes, XxlRpcRequest.class);
  47. requestId = xxlRpcRequest.getRequestId();
  48. // 处理请求
  49. XxlRpcResponse xxlRpcResponse = xxlRpcProviderFactory.invokeService(xxlRpcRequest);
  50. // response serialize
  51. byte[] responseBytes = xxlRpcProviderFactory.getSerializer().serialize(xxlRpcResponse);
  52. // response-write
  53. writeResponse(ctx, keepAlive, responseBytes);
  54. }
  55. } catch (Exception e) {
  56. ......
  57. }
  58. }
  59. ......
  60.  
  61. }
  1. public XxlRpcResponse invokeService(XxlRpcRequest xxlRpcRequest) {
  2.  
  3. ......
  4. String serviceKey = makeServiceKey(xxlRpcRequest.getClassName(), xxlRpcRequest.getVersion());
  5. //取出ExecutorBizImpl实例
  6. Object serviceBean = serviceData.get(serviceKey);
  7. ......
  8. try {
  9. // 反射调用ExecutorBizImpl对象run方法
  10. Class<?> serviceClass = serviceBean.getClass();
  11. String methodName = xxlRpcRequest.getMethodName();
  12. Class<?>[] parameterTypes = xxlRpcRequest.getParameterTypes();
  13. Object[] parameters = xxlRpcRequest.getParameters();
  14. Method method = serviceClass.getMethod(methodName, parameterTypes);
  15. method.setAccessible(true);
  16. Object result = method.invoke(serviceBean, parameters);
  17. xxlRpcResponse.setResult(result);
  18. } catch (Throwable t) {
  19. // catch error
  20. logger.error("xxl-rpc provider invokeService error.", t);
  21. xxlRpcResponse.setErrorMsg(ThrowableUtil.toString(t));
  22. }
  23. return xxlRpcResponse;
  24.  
  25. }
  1. public class ExecutorBizImpl implements ExecutorBiz {
  2.  
  3. ......
  4. @Override
  5. public ReturnT<String> run(TriggerParam triggerParam) {
  6.  
  7. // 缓存获取JobThread对象
  8. JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
  9. IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
  10. String removeOldReason = null;
  11.  
  12. GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
  13. if (GlueTypeEnum.BEAN == glueTypeEnum) {
  14. // 缓存中获取IJobHandler对象(即我们的业务job)
  15. // 之前通过扫描注解存入缓存
  16. IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
  17. // valid old jobThread
  18. if (jobThread!=null && jobHandler != newJobHandler) {
  19. // change handler, need kill old thread
  20. removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
  21. jobThread = null;
  22. jobHandler = null;
  23. }
  24. // valid handler
  25. if (jobHandler == null) {
  26. jobHandler = newJobHandler;
  27. if (jobHandler == null) {
  28. return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
  29. }
  30. }
  31. } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
  32. // valid old jobThread
  33. if (jobThread != null &&
  34. !(jobThread.getHandler() instanceof GlueJobHandler
  35. && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
  36. // change handler or gluesource updated, need kill old thread
  37. removeOldReason = "change job source or glue type, and terminate the old job thread.";
  38. jobThread = null;
  39. jobHandler = null;
  40. }
  41.  
  42. // valid handler
  43. if (jobHandler == null) {
  44. try {
  45. //从DB中获取源码,通过groovy进行加载并实例化
  46. IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
  47. jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
  48. } catch (Exception e) {
  49. logger.error(e.getMessage(), e);
  50. return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
  51. }
  52. }
  53. } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
  54. // valid old jobThread
  55. if (jobThread != null &&
  56. !(jobThread.getHandler() instanceof ScriptJobHandler
  57. && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
  58. // change script or gluesource updated, need kill old thread
  59. removeOldReason = "change job source or glue type, and terminate the old job thread.";
  60. jobThread = null;
  61. jobHandler = null;
  62. }
  63.  
  64. // valid handler
  65. if (jobHandler == null) {
  66. //读取脚本,写入文件,最终执行通过commons-exec
  67. jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
  68. }
  69. } else {
  70. return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");
  71. }
  72.  
  73. // 阻塞策略
  74. if (jobThread != null) {
  75. ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
  76. if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
  77. // 丢弃后续调度
  78. if (jobThread.isRunningOrHasQueue()) {
  79. return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
  80. }
  81. } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
  82. // 覆盖之前调度
  83. if (jobThread.isRunningOrHasQueue()) {
  84. removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();
  85.  
  86. jobThread = null;
  87. }
  88. } else {
  89. // just queue trigger
  90. }
  91. }
  92.  
  93. // 第一次执行或者是覆盖之前调度策略
  94. if (jobThread == null) {
  95. //开启线程,执行任务
  96. jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
  97. }
  98.  
  99. // 触发任务入队
  100. ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
  101. return pushResult;
  102. }
  103. }

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

  1. ......
  2. private static ConcurrentHashMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();
  3.  
  4. public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){
  5. //新线程执行
  6. JobThread newJobThread = new JobThread(jobId, handler);
  7. //线程执行
  8. newJobThread.start();
  9. logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});
  10. //放入缓存
  11. JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);
  12. if (oldJobThread != null) {
  13. //旧任务线程停止,覆盖策略
  14. oldJobThread.toStop(removeOldReason);
  15. oldJobThread.interrupt();
  16. }
  17. return newJobThread;
  18. }
  19. ......
  1. public class JobThread extends Thread{
  2.  
  3. private static Logger logger = LoggerFactory.getLogger(JobThread.class);
  4. private int jobId;
  5. private IJobHandler handler;
  6. private LinkedBlockingQueue<TriggerParam> triggerQueue;
  7. private Set<Integer> triggerLogIdSet; // avoid repeat trigger for the same TRIGGER_LOG_ID
  8. private volatile boolean toStop = false;
  9. private String stopReason;
  10. private boolean running = false; // if running job
  11. private int idleTimes = 0; // idel times
  12.  
  13. public JobThread(int jobId, IJobHandler handler) {
  14. this.jobId = jobId;
  15. this.handler = handler;
  16. this.triggerQueue = new LinkedBlockingQueue<TriggerParam>();
  17. this.triggerLogIdSet = Collections.synchronizedSet(new HashSet<Integer>());
  18. }
  19.  
  20. public IJobHandler getHandler() {
  21. return handler;
  22. }
  23.  
  24. /**
  25. * trigger入队,执行的时候出队
  26. *
  27. * @param triggerParam
  28. * @return
  29. */
  30. public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) {
  31. // avoid repeat
  32. if (triggerLogIdSet.contains(triggerParam.getLogId())) {
  33. logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId());
  34. return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId());
  35. }
  36. triggerLogIdSet.add(triggerParam.getLogId());
  37. triggerQueue.add(triggerParam);
  38. return ReturnT.SUCCESS;
  39. }
  40.  
  41. /**
  42. * kill job thread
  43. *
  44. * @param stopReason
  45. */
  46. public void toStop(String stopReason) {
  47. /**
  48. * Thread.interrupt只支持终止线程的阻塞状态(wait、join、sleep),
  49. * 在阻塞出抛出InterruptedException异常,但是并不会终止运行的线程本身;
  50. * 所以需要注意,此处彻底销毁本线程,需要通过共享变量方式;
  51. */
  52. this.toStop = true;
  53. this.stopReason = stopReason;
  54. }
  55.  
  56. /**
  57. * is running job
  58. * @return
  59. */
  60. public boolean isRunningOrHasQueue() {
  61. return running || triggerQueue.size()>0;
  62. }
  63.  
  64. @Override
  65. public void run() {
  66. // init
  67. try {
  68. handler.init();
  69. } catch (Throwable e) {
  70. logger.error(e.getMessage(), e);
  71. }
  72.  
  73. // execute
  74. while(!toStop){
  75. running = false;
  76. idleTimes++;
  77. TriggerParam triggerParam = null;
  78. ReturnT<String> executeResult = null;
  79. try {
  80. //出队消费,3秒获取不到就返回null
  81. triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
  82. if (triggerParam!=null) {
  83. running = true;
  84. idleTimes = 0;
  85. triggerLogIdSet.remove(triggerParam.getLogId());
  86. // 日志 "logPath/yyyy-MM-dd/9999.log"
  87. String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId());
  88. XxlJobFileAppender.contextHolder.set(logFileName);
  89. //任务分片数据
  90. ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()));
  91. // execute
  92. XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + triggerParam.getExecutorParams());
  93. if (triggerParam.getExecutorTimeout() > 0) {
  94. //有超时限制
  95. Thread futureThread = null;
  96. try {
  97. final TriggerParam triggerParamTmp = triggerParam;
  98. FutureTask<ReturnT<String>> futureTask = new FutureTask<ReturnT<String>>(new Callable<ReturnT<String>>() {
  99. @Override
  100. public ReturnT<String> call() throws Exception {
  101. //执行业务job
  102. return handler.execute(triggerParamTmp.getExecutorParams());
  103. }
  104. });
  105. futureThread = new Thread(futureTask);
  106. futureThread.start();
  107. //可能超时
  108. executeResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
  109. } catch (TimeoutException e) {
  110. XxlJobLogger.log("<br>----------- xxl-job job execute timeout");
  111. XxlJobLogger.log(e);
  112. executeResult = new ReturnT<String>(IJobHandler.FAIL_TIMEOUT.getCode(), "job execute timeout ");
  113. } finally {
  114. futureThread.interrupt();
  115. }
  116. } else {
  117. // 无超时限制的,直接执行
  118. executeResult = handler.execute(triggerParam.getExecutorParams());
  119. }
  120. ......

  121. // destroy
  122. try {
  123. handler.destroy();
  124. } catch (Throwable e) {
  125. logger.error(e.getMessage(), e);
  126. }
  127. logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
  128. }
  129. }

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

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

从定时器的选型,到透过源码看XXL-Job(下)的更多相关文章

  1. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

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

    此内容来自一位好朋友的分享,也是当初建议我写博客提升的朋友.内容只做转载,未做修改. 定时任务选型 背景 目前项目定时任务采用Spring Task实现,随着项目需求的迭代,新增的定时任务也越来越多. ...

  3. 透过源码看懂Flink核心框架的执行流程

    前言 Flink是大数据处理领域最近很火的一个开源的分布式.高性能的流式处理框架,其对数据的处理可以达到毫秒级别.本文以一个来自官网的WordCount例子为引,全面阐述flink的核心架构及执行流程 ...

  4. 透过源码看看Redis中如何计算QPS

    通常我们采集Redis的性能数据时,或者想要知道Redis当前的性能如何时,需要知道这个实例的QPS数据,那么这个QPS数据是如何计算的呢?我们都有哪些办法或者这个QPS ? QPS顾名思义就是每秒执 ...

  5. 透过源码分析ArrayList运作原理

    List接口的主要实现类ArrayList,是线程不安全的,执行效率高:底层基于Object[] elementData 实现,是一个动态数组,它的容量能动态增加和减少.可以通过元素下标访问对象,使用 ...

  6. 通过源码看android系列之AsyncTask

    整天用AsyncTask,但它的内部原理一直没有特意去研究,今天趁着有时间,码下它的原理. 具体用法就不再说明,相信大家已经用得很熟练了,我们今天就从它怎么运行开始说.先新建好我们的AsyncTask ...

  7. 通过源码看android系列之multidex库

    我们在开发项目时,喜欢引入好多的第三方包,大大的方便了我们的开发,但同时,因为android方法总数的限制,不能超过65k,然而呢,随着我们的开发,65k最终还是会超过,所以,google就给出了这个 ...

  8. 通过源码看原理之 selenium

    # selenium的历史1. selenium1.x:这个时候的selenium,使用的是JavaScript注入技术与浏览器打交道,需要Selenium RC启动一个Server,将操作Web元素 ...

  9. 通过源码了解ASP.NET MVC 几种Filter的执行过程

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...

随机推荐

  1. Spring容器的创建原理

    1.new ioc容器(AnnotationConfigApplicationContext 注解ioc) 2.refresh()方法调用 2.1 prepareRefresh()刷新前的预处理 a: ...

  2. P1048 数字加密

    P1048 数字加密 转跳点:

  3. P1002 写出这个数(Basic Level)

    转跳点:

  4. C++学习链表

    #include"pch.h" #include<iostream> #include<string> using namespace std; struc ...

  5. 13.swoole学习笔记--DNS查询

    <?php //执行DNS查询 swoole_async_dns_lookup("www.baidu.com",function($host,$ip){ echo $ip; ...

  6. 3.2Adding custom methods to mappers(在映射器中添加自定义方法)

    3.2Adding custom methods to mappers(在映射器中添加自定义方法) 有些情况下,我们需要实现一些MapStruct无法直接自动生成的复杂类型间映射.一种方式是复用其他已 ...

  7. MVC 中引用Angularjs

    首先在Maname NuGet Packages中 安装相应的包,我用的是作者为 AngualrJS Team的 随后在相应的Scripts中会出现对应文件. 如果只在某一个页面中使用Angualrj ...

  8. 第七篇:Python3连接MySQL

    第七篇:Python3连接MySQL 连接数据库 注意事项 在进行本文以下内容之前需要注意: 你有一个MySQL数据库,并且已经启动. 你有可以连接该数据库的用户名和密码 你有一个有权限操作的data ...

  9. UVA - 12545 Bits Equalizer (比特变换器)(贪心)

    题意:输入两个等长(长度不超过100)的串S和T,其中S包含字符0,1,?,但T只包含0和1,你的任务是用尽量少的步数把S变成T.有以下3种操作: 1.把S中的0变成1. 2.把S中的“?”变成0或1 ...

  10. POJ 1284:Primitive Roots 求原根的数量

    Primitive Roots Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 3381   Accepted: 1980 D ...