记录定时任务迁移xxl-job的过程和理解

         一般定时任务使用的是基于quartz或者spring-scheduler的,能够满足大部分的开发需求。但是像手动执行一次,执行情况监测,进程阻塞停止等维护需求就显得无能为力了。无意间在gitee.com上发现了一个很好满足以上需求的项目,来自许雪里开源的一个轻量级分布式任务调度平台xxl-job。gitee地址:https://gitee.com/xuxueli0323/xxl-job,主页为:http://www.xuxueli.com/xxl-job/。本文只触及到其简单的功能使用,更多功能和更深层次的理解请参考源码。

记录定时任务迁移xxl-job的过程和理解_第1张图片

记录定时任务迁移xxl-job的过程和理解_第2张图片

       通过其demo使用和源码的梳理,基本搞明白了其运行原理。其主要分为任务执行器和任务调度器。任务执行器接收调度指令,执行相应的方法。任务调度器根据事先设定的每个job handler 的cron表达式调度任务执行器。如果调度失败可以根据配置再调度其他的执行器(集群模式下)。在集群模式下,相同appname的多个执行器组成一个执行器集群,Handler与执行器集群之间通过appname绑定。一个执行器相当于一个runner实体,一个runner可以有多个Handler(job),他们都由调度器管理,在调度器中指定Handler与执行器之间的关系。

       执行器项目一般集成到自己的项目中,调度器是一个后台管理项目,二者分别部署到两个主机,通过rpc交互(xxl-rpc):调度器通过rpc的9999端口调用执行器,执行器回调走8080http端口调用调度中心。任务调度中心通过数据库管理执行器和job等实体信息,所以需要一个数据库。直接通过

java -jar xxl-job-admin-2.1.1-SNAPSHOT.jar &

的方式运行调度中心。配置执行器和任务。

记录定时任务迁移xxl-job的过程和理解_第3张图片

记录定时任务迁移xxl-job的过程和理解_第4张图片

路由策略

记录定时任务迁移xxl-job的过程和理解_第5张图片

执行器参考其示例项目,引入xxl-job-core,初始化XxlJobExecutor,注册jobhandler即可。

我们以前的项目是基于quartz的,框架基于vertx,所以选择了FrameLess这种集成方式。

Set> jobHandlerClasses = ClassUtil.scanPackageBySuper("xx.xx.xxx.xxx.package", true, IJobHandler.class);

        for (Class jobHandlerClass : jobHandlerClasses) {
            String name = StrUtil.lowerFirst(jobHandlerClass.getSimpleName());
            if(jobHandlerClass.isAnnotationPresent(JobHandler.class)){
                String value = jobHandlerClass.getAnnotation(JobHandler.class).value();
                if(!"".equals(value)){
                    name = value;
                }
            }
            IJobHandler jobHandler = BeanUtil.newInstance(jobHandlerClass);
            XxlJobExecutor.registJobHandler(name , jobHandler);
        }

        // load executor prop
        //Properties xxlJobProp = loadProperties("xxl-job-executor.properties");
        Prop prop = PropertiesUtils.use(CONFIG_PATH + "xxl-job-executor.properties");

        // init executor
        xxlJobExecutor = new XxlJobExecutor();
        xxlJobExecutor.setAdminAddresses(prop.get("xxl.job.admin.addresses"));
        xxlJobExecutor.setAppName(prop.get("xxl.job.executor.appname"));
        xxlJobExecutor.setIp(prop.get("xxl.job.executor.ip"));
        xxlJobExecutor.setPort(prop.getInt("xxl.job.executor.port"));
        xxlJobExecutor.setAccessToken(prop.get("xxl.job.accessToken"));
        xxlJobExecutor.setLogPath(prop.get("xxl.job.executor.logpath"));
        xxlJobExecutor.setLogRetentionDays(prop.getInt("xxl.job.executor.logretentiondays"));

        // start executor
        try {
            xxlJobExecutor.start();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

以前的job不动,重新写jobhandler,然后将以前的job关闭,注册这个新的handler到调度器中。jobhandler中调用以前的service方法,先改几个不太重要的job,比如清理类的job,执不执行和多次执行都没啥影响的,运行无误之后再迁移其他所有的job。如此,可以非常平滑地迁移,迁移的风险也降到最低。job和jobhandler之间其实相当于两个马甲,都是调用service来完成具体的事情,从此也能看出代码规范的重要性。

public class DemoJobHandler extends IJobHandler {

	@Override
	public ReturnT execute(String param) throws Exception {
		XxlJobLogger.log("XXL-JOB, Hello World.");

		for (int i = 0; i < 5; i++) {
			XxlJobLogger.log("beat at:" + i);
			TimeUnit.SECONDS.sleep(2);
		}
        
        
        xxxxx.service.doSomething();

		return SUCCESS;
	}

}

 

FrameLess这种集成方式有一个非常重要的问题,就是如何保持程序的不退出?

第一种方式:利用quartz的job线程,即至少运行一个job,这种方式比较简单,但是不够优雅。

第二种方式:写一个hold住程序的类,jvm退出的两个理由是1)不再拥有前台进程和2)程序调用了exit,所以只要保证程序有一个前台进程,jvm就不会退出。

/**
 * 使程序不退出,保证至少一个前台进程
 * @see https://dubbo.apache.org/zh-cn/blog/spring-boot-dubbo-start-stop-analysis.html
 * @author xiongshiyan at 2019/10/16 , contact me with email [email protected] or phone 15208384257
 */
public class HoldProcessor {
    private volatile boolean stopAwait = false;
    /**
     * Thread that currently is inside our await() method.
     */
    private volatile Thread awaitThread = null;

    /**
     * 开始等待
     */
    public void startAwait(){
        Thread awaitThread = new Thread(this::await,"hold-process-thread");
        awaitThread.setContextClassLoader(getClass().getClassLoader());
        //这一步很关键,保证至少一个前台进程
        awaitThread.setDaemon(false);
        awaitThread.start();
    }

    /**
     * 停止等待,退出程序,一般放在shutdown hook中执行
     * @see Runtime#addShutdownHook(Thread)
     */
    public void stopAwait() {
        //此变量
        stopAwait=true;
        Thread t = awaitThread;
        if (null != t) {
            t.interrupt();
            try {
                t.join(1000);
            } catch (InterruptedException e) {
                // Ignored
            }
        }
    }

    private void await(){
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch( InterruptedException ex ) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
    }
}
FrameLessXxlJobConfig.getInstance().initXxlJobExecutor();

HoldProcessor holdProcessor = new HoldProcessor();
holdProcessor.startAwait();
logger.info("程序开始等待");
Runtime.getRuntime().addShutdownHook(new Thread(()->{
      logger.info("收到kill 信号,执行清理程序");
      //在关闭的时候释放资源
      FrameLessXxlJobConfig.getInstance().destroyXxlJobExecutor();
      holdProcessor.stopAwait();
}));
参考:https://dubbo.apache.org/zh-cn/blog/spring-boot-dubbo-start-stop-analysis.html


基于以上的原因,程序不要使用kill -9 pid的方式关闭,这样会收不到关闭信号无法执行钩子程序。

你可能感兴趣的:(SpringBoot扩展,JFinal扩展,Quartz,quartz,xxl-job)