XXL-Job源码分析(I)--调度中心启动

前言

XXL-Job是一个轻量的分布式任务调度平台,任务的定时调度管理是基于开源定时任务调度框架Quarts来实现的,任务的调度执行采用注册和RPC的方式实现,将任务的管理和执行进行了分离。我们先来了解一下调度中心的源码,调度中心主要负责任务的管理,本身并不复杂任务的业务逻辑,任务触发后,调度中心根据配置的相应规则将任务分配到执行器,执行器处理相应的业务逻辑。

调度中心启动流程

1、初始化任务调度器

XxlJobDynamicScheduler对Quarts的调度器进行封装,并在XxlJobDynamicSchedulers实例化后运行start方法,完成调度中心的启动。

XxlJobDynamicSchedulerConfig.java


    @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;
    }

2、start方法

由系统的java config类可知,调度中心的启动的初始化工作由start方法实现,start方法源码如下,主要有三个步骤:
1)、启动执行器注册发现监听线程,该线程负责执行器实例的注册与下线;
2)、启动任务执行失败监听线程,负责处理执行失败的任务;
3)、初始化RPC,调度中心与执行器通过RPC方式通信。
同时可以看到JobRegistryMonitorHelper和JobFailMonitorHelper都是单例模式实现。

    public void start() throws Exception {
        // valid 验证Quarts调度器是否为空
        Assert.notNull(scheduler, "quartz scheduler is null");

        // init i18n 国际化
        initI18n();

        // admin registry monitor run 自动注册线程,完成执行器的自动发现和注册
        JobRegistryMonitorHelper.getInstance().start();

        // admin monitor run 启动任务执行失败监控线程
        JobFailMonitorHelper.getInstance().start();

        // admin-server 初始化RpcProvider
        initRpcProvider();

        logger.info(">>>>>>>>> init xxl-job admin success.");
    }
3、执行器注册监听线程

这里执行器注册的方式是,执行器启动后会以同样的心跳检测间隔在数据库中更新自己的信息,然后调度中心的监听线程从数据库中获取执行器的注册信息,更新到内存中。

private Thread registryThread;
	private volatile boolean toStop = false;
	public void start(){
		registryThread = new Thread(new Runnable() {
			@Override
			public void run() {
				while (!toStop) {
					try {
						// 自动注册任务组,从数据库中获取保存的任务组,这里的任务组代表一组执行器
						List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
						if (groupList!=null && !groupList.isEmpty()) {

							// 移除已经下线的执行器和调度中心节点,心跳检测间隔默认为10s一次,连续检测三次节点不在线,就移除,删除数据库中节点的信息
							XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(RegistryConfig.DEAD_TIMEOUT);

							// 从数据库中获取在线节点的ip地址,并刷新内存中数据
							HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
							List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT);
							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);
									}
								}
							}

							// 刷新每个任务组中,在线节点的IP信息
							for (XxlJobGroup group: groupList) {
								List<String> registryList = appAddressMap.get(group.getAppName());
								String addressListStr = null;
								if (registryList!=null && !registryList.isEmpty()) {
									Collections.sort(registryList);
									addressListStr = StringUtils.join(registryList, ",");
								}
								group.setAddressList(addressListStr);
								XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
							}
						}
					} catch (Exception e) {
						if (!toStop) {
							logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
						}
					}
					// 心跳检测,十秒一次,连续三次不在线就判断为下线
					try {
						TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
					} catch (InterruptedException e) {
						if (!toStop) {
							logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
						}
					}
				}
				logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
			}
		});
		// 把线程设置为守护线程
		registryThread.setDaemon(true);
		registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
		// 启动线程
		registryThread.start();
	}

4、任务执行失败监听

这部分的实现原理和执行器注册监听原理一样,从数据库中获取执行失败的任务,然后进行相应的处理。

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

			@Override
			public void run() {

				// monitor
				while (!toStop) {
					try {

						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);
								// 从数据库中读取失败任务的信息
								XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());

								// 1、失败重试,若配置了失败重试,则重新触发任务
								if (log.getExecutorFailRetryCount() > 0) {
									JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), null);
									String retryMsg = "

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<<
"
; log.setTriggerMsg(log.getTriggerMsg() + retryMsg); XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log); } // 2、失败告警,根据配置决定是否告警 int newAlarmStatus = 0; // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败 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) { if (!toStop) { logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e); } } } logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop"); } }); monitorThread.setDaemon(true); monitorThread.setName("xxl-job, admin JobFailMonitorHelper"); monitorThread.start(); }
5、Rpc初始化

调度平台的Rpc服务是基于XXL-RPC实现,添加了一个服务AdminBiz,该服务负责执行器的注册及移除。
前面提到的执行器注册监听是通过数据库操作实现的,在执行器注册时便是调用AdminBiz这个接口将自己的注册信息写入数据库的。

private void initRpcProvider(){
        // 工厂初始化
        XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
        xxlRpcProviderFactory.initConfig(
                NetEnum.NETTY_HTTP,
                Serializer.SerializeEnum.HESSIAN.getSerializer(),
                null,
                0,
                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
                null,
                null);

        // 添加服务
        xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());

        // servlet handler
        servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
    }

总结

总的来说,调度中心在启动时主要做了三件事,第一、启动注册监听,通过心跳检测管理执行器节点的上线及下线,第二、启动任务执行失败监听,对失败任务进行处理,最后启动RPCProvider,提供执行器注册服务

你可能感兴趣的:(分布式任务调度)