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

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

       通过其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-core,初始化XxlJobExecutor,注册jobhandler即可。

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

Set<Class<?>> 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<String> 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 yanshixiong@126.com 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的方式关闭,这样会收不到关闭信号无法执行钩子程序。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
xxl-job-admin-spring-boot是将xuxueli的xxl-job-admin项目重新设计并适配到spring boot框架的工作。 首先,xxl-job-admin是一个用于管理任务调度的平台,能够实现任务的添加、暂停、恢复、删除等功能。而spring boot是一种快速构建应用程序的框架,因其简洁的配置和开发方式而广受欢迎。 在将xxl-job-admin迁移到spring boot框架时,需要进行一系列修改和适配。首先,要将原有的项目结构转化为符合spring boot规范的结构,包括调整包的命名、重新组织项目目录结构以及调整Maven或Gradle构建文件等。这样可以更好地利用spring boot的自动配置和约定,提高开发效率。 其次,需要调整原有的依赖关系和配置文件。由于spring boot采用自动配置的方式,我们需要根据xxl-job-admin的需求来配置相应的依赖和属性。这可能涉及到数据库连接、缓存、安全认证等方面的设置。同时,还要考虑与其他可能的项目组件的集成,如使用spring security实现权限控制。 最后,还要对原有的代码进行调整和优化。由于spring boot具有更好的集成性和可扩展性,我们可以使用spring boot提供的组件和注解来简化代码,提高系统性能。例如,使用spring的注解和AOP来处理事务,使用spring data来简化数据库操作等。 通过将xxl-job-admin迁移到spring boot框架,可以充分发挥spring boot的优势,使得项目更易于维护和扩展。同时,也能够与其他基于spring boot的项目更好地整合,加强协作效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值