分布式任务调度平台xxl job应用及源码分析--含本地集群

5 篇文章 0 订阅
2 篇文章 0 订阅

一、框架分类

常见定时任务框架可按如下区分:
单机:Timer、ScheduledExecutorService、Spring定时框架
分布式:Quartz、SpringBatch、TBSchedule、elastic-job、xxl-job、Saturn
另外有些小众的框架:uncode-schedule、LTS、TBSchedule、Opencron、Antares、sia-task

二、常用框架特点:

  1. Quartz

Java定时任务标准。但Quartz关注点在于定时任务而非数据,并无一套根据数据处理而定制化的流程。虽然Quartz可以基于数据库实现作业的高可用,但缺少分布式并行调度的功能,且缺少分片处理功能。

  1. TBSchedule

阿里早期开源的分布式任务调度系统。代码略陈旧,使用timer而非线程池执行任务调度。众所周知,timer在处理异常状况时是有缺陷的。而且TBSchedule作业类型较为单一,只能是获取/处理数据一种模式。还有就是文档缺失比较严重。

  1. Saturn

Saturn是唯品会在github开源的一款分布式任务调度产品。它是基于当当elastic-job 1.0版本来开发的,其上完善了一些功能和添加了一些新的feature。支持多语言开发 python、Go、Shell、Java、Php。管理控制台和数据统计分析更加完善,技术文档较少 , 该框架是2016年由唯品会的研发团队基于elastic-job开发而来。

  1. elastic-job

当当开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片,并且可以支持云开发

  1. xxl-job

是大众点评员工徐雪里于2015年发布的分布式任务调度平台,是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。文档齐全,教程很多,遇到问题也比较容易找到解决方案.
因为e-job需要至少三台的zookeeper,以及结合项目实际情况,暂时采用从quartz改造成xxl-job管理定时任务,以下将从xxl-job的概述,使用,以及遇到的一些坑进行探索,其他框架暂不做讨论。
官方文档:官方文档

三、本地单机启动

3.0 准备工作

先从官网上将项目down下来:

官网地址:https://github.com/xuxueli/xxl-job

将下载后的zip文件解压,用Idea打开
在这里插入图片描述
脚本文件位置如上图所示,在navicat中执行数据库脚本,生成如下所示xxl-job相关表
在这里插入图片描述
导入脚本后,在使用项目前,先看下其架构图
在这里插入图片描述

如上图所示,xxl-job分为两块:调度中心、执行器。
示例项目结构,如下图所示:

xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)

  1. :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
  2. :xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用;
  3. :xxl-job-executor-sample-frameless:无框架版本;

在这里插入图片描述
另外,中间的xxl-job-core在maven上也可以下载其jar包。

3.1 调度中心本地部署

  1. 环境:Maven3+、Jdk1.8+、Mysql5.7+
  2. 配置部署“调度中心”
  3. 调度中心项目:xxl-job-admin
  4. 作用:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。

3.1.1 步骤一:调度中心配置修改

  1. 配置文件地位置:

/xxl-job/xxl-job-admin/src/main/resources/application.properties

  1. 修改配置:

如果仅入门,可仅修改数据库配置,数据库为本地mysql的情况下不用修改,如果为公司测试环境,修改为公司测试环境的数据库配置即可。

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root_pwd
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

3.1.2 步骤二:部署项目:

  1. 启动

如果已经正确进行上述配置,可将项目编译,在Idea上启动。

  1. 访问

经过以上两步,即可完成本地单机版调度中心的部署,访问调度中心地址:http://localhost:8080/xxl-job-admin (该地址执行器将会使用到,作为回调地址)
默认登录账号 “admin/123456”, 登录后运行界面如下图所示。
在这里插入图片描述
如果看到这个界面,调度中心即部署成功。

3.2 执行器本地部署

3.2.1 SpringBoot部署执行器

“执行器”项目:xxl-job-executor-sample-springboot (提供多种类型执行器供选择,现以 springboot 版本为例,直接使用演示)
作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。

步骤一:maven依赖

确认pom文件中引入了 “xxl-job-core” 的maven依赖,且需与调度中心的xxl-job-core版本保持一致;

步骤二:执行器配置
  1. 执行器配置,配置文件地址:

/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties

  1. 执行器配置:

如无特殊需求,仅需配置调度中心地址以及appname如下所示:

### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册""任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample

注意:其中appname为执行器唯一标识,后面配置执行器时会用到。

步骤三:执行器组件配置
  1. 执行器组件,配置文件地址

(下载的源码里已存在,使用即可):

/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java

  1. 配置内容说明:
@Bean
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;
}
步骤四:部署执行器项目:

如果已经正确进行上述配置,可将执行器项目编译,Idea启动项目(启动SpringBoot项目)。
如项目正常启动,且无报错,则SpringBoot方式的执行器单机部署完毕。

3.2.2 Spring部署执行器

Spring的配置和启动与SpringBoot类似,只是需要用Idea部署到本地tomcat启动,可百度搜索Idea启动war项目。

至此,一套标准,最简单的调度中心和执行器就启动部署完成,接下来进行配置,让job生效执行。

3.2.3 调度中心配置

  1. 背景

虽然我们已经部署了调度中心和执行器,但是在没有任何配置的情况下,调度中心是不知道执行器的存在的,所以我们首先需要配置对应的执行器。

  1. 配置执行器

新建执行器,如下图所示进行新增,其中AppName必须与开始我们在执行器项目种配置的appname一致。
在这里插入图片描述

  1. 配置任务

由于GitHub上down下来的代码有Bean模式job任务的示例,下面我们以demoJobHandler作为例子来新建任务。
新建任务,如下图所示
在这里插入图片描述
简单使用的话,仅需填写标黄项,执行器选择我们开始配置的”示例执行器",任务描述建议与jobHandler保持一致,方便区分,模式暂时选择Bean模式。

至此,点击任务的操作–启动按钮,就可以让项目协同跑起来了,即:

  1. 调度中心按配置在规定的时间触发调度任务。
  2. 执行器收到调度中心的请求后,进行任务的执行以及执行结果的回调等等。

后面讨论的所有问题,基本都是围绕着上面的这些组件进行探讨。

四、本地集群启动

4.1 方案

安装nginx软件、启动两台调度中心、启动三台执行器(已集成xxl-job执行器的项目)

4.2 nginx

4.2.1. 配置

nginx.conf配置文件修改,新增upstream负载均衡,新增location 负载到这两个本地服务。

http {
	***
	upstream xxlAdmin{
		server 127.0.0.1:10086;
		server 127.0.0.1:10087;
	}
	***
    server {

		***
		location /xxl-job-admin {
            proxy_pass http://xxlAdmin/xxl-job-admin;
        }
		***
}

4.2.2. 启动

windows启动nginx(双击启动文件或命令启动)

4.2.3. 访问

访问地址:http://localhost/index.html,看到Welcome to nginx!则nginx启动成功。

4.3 调度中心

4.3.1 启动

启动两台调度中心,其中第一台与单机启动一样,另外一台需重新在新建一个SpringBoot服务配置,如下图所示,新建后除常规设置与单机一致外,还需要设置图中圈出参数指定一个与第一台不同的端口号,且需要保证该端口不被其他线程占用。
在这里插入图片描述

4.3.2 访问

若两个服务都正常启动,则访问nginx地址:http://localhost/xxl-job-admin/jobinfo即可通过nginx访问调度中心,达到负载均衡的目的。

4.4 执行器

在这里插入图片描述

4.4.1 启动

第一台与单机一致,启动一个后,把配置文件的端口号改成9998,再新增配置一个服务,修改server.port=8083,不要与其他服务冲突,启动第二个服务,9997同理。

4.4.2 查看注册效果

若三个服务都成功启动,且如果我们按appname在调度中心页面配置过对应的执行器,则在1分钟以内能看到三台服务器已经成功注册到了调度中心,点击查看即可显示,如下图所示。
在这里插入图片描述

4.4.3 总结

  1. 至此,已完成本地调度中心的集群(2台),以及执行器的横向扩展(3台)。
  2. 当我们将任务的路由策略改成轮询时,则会一次在这三台执行器上执行,且每次有且仅有一台会触发执行。
  3. 另外,如果执行器集成在Spring项目中,可按如下图所示配置三台tomcat,注意修改服务的端口号,三台机器不能冲突,且不能与其他应用冲突,启动一台后修改9998,再启动第二台,9997同理。

在这里插入图片描述

  1. 当然咯,线上的测试环境或者生产环境不会有这种繁琐的端口配置问题,因为每个服务都是在不同机器上部署,就不会造成端口冲突问题。

五、源码解析

以下将简单从源码角度对调度中心和执行器进行探讨

调度中心源码

如下图所示,调度中心项目启动后,如下为配置入口,实现了InitializingBean接口,spring容器在初始化的时候会调用afterPropertiesSet方法,最终会调用XxlJobScheduler的init()方法。
在这里插入图片描述
分析XxlJobScheduler.init()

public void init() throws Exception {
        // init i18n
        initI18n();

        // admin trigger pool start
        JobTriggerPoolHelper.toStart();

        // admin registry monitor run
        JobRegistryHelper.getInstance().start();

        // admin fail-monitor run
        JobFailMonitorHelper.getInstance().start();

        // admin lose-monitor run ( depend on JobTriggerPoolHelper )
        JobCompleteHelper.getInstance().start();

        // admin log report start
        JobLogReportHelper.getInstance().start();

        // start-schedule  ( depend on JobTriggerPoolHelper )
        JobScheduleHelper.getInstance().start();

        logger.info(">>>>>>>>> init xxl-job admin success.");
    }
  • initI18n();
    初始化新增任务页面中的阻塞处理策略相关的国际化信息。如果没有配置国际化语言则默认为中文。

  • JobTriggerPoolHelper.toStart();
    初始化快慢两个线程池:fastTriggerPool和slowTriggerPool。当任务执行的时候默认使用快线程池fastTriggerPool;当任务执行失败并且重试次数达到阈值的时候就会使用慢线程池slowTriggerPool来执行任务

  • JobRegistryHelper.getInstance().start();
    创建一个守护线程管理注册地址(每30秒执行一次)

  • JobFailMonitorHelper.getInstance().start();
    预警守护线程(每十秒工作一次),可增加自定义的预警类型,默认只有邮件。

  • JobCompleteHelper.getInstance().start();
    任务结果丢失处理,另外有些版本是JobLosedMonitorHelper.getInstance().start(),需要注意区分。

  • JobLogReportHelper.getInstance().start();
    创建一个日志守护线程(每一分钟工作一次)
    刷新前三天的日志相关报表数据
    清理N天前的日志记录(N就是我们配置的xxl.job.logretentiondays参数,必须大于7,否则无效)

  • JobScheduleHelper.getInstance().start();
    创建了两个调度相关守护线程scheduleThread(5秒内)和ringThread(处理耗时较长)

执行器源码

在业务定时任务系统,即实际工作中迭代的项目:

  1. 引入xxl-job的依赖配置,或导入jar包
  2. 新增执行器组件配置类XxlJobConfig.java,其中配置了核心类XxlJobSpringExecutor。(注意,如果使用的是Spring4.1以下的,则无法使用XxlJobSpringExecutor,因为较早的Spring版本meiyouSmartInitializingSingleton类,也就无法进行这种扫描的方式管理JobHandler)
    Spring4.1以上版本,可使用如下推荐方式生成Excutor:
@Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

Spring早期版本可使用如下这种方式(缺点:硬编码):

 @Bean(initMethod = "start", destroyMethod = "destroy")
    public XxlJobExecutor xxlJobExecutor() {
        XxlJobExecutor.registJobHandler("DemoJob1", new DemoJob1());//
        XxlJobExecutor.registJobHandler("DemoJob2", new DemoJob2());//
        //...
        
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
        xxlJobExecutor.setIp(ip);
        xxlJobExecutor.setPort(port);
        xxlJobExecutor.setAppname(appname);
        xxlJobExecutor.setAdminAddresses(addresses);
        xxlJobExecutor.setLogPath(logpath);
        xxlJobExecutor.setAccessToken(accessToken);
        return xxlJobExecutor;
    }
  1. 新增IJobHandler继承类,类中有带 @XxlJob(“xxx”)注解的方法,一个IJobHandler即为一个job任务,其概念与quartz任务类似。
    XxlJobSpringExecutor核心类分析
 @Override
    public void afterSingletonsInstantiated() {

        // init JobHandler Repository
        /*initJobHandlerRepository(applicationContext);*/

        // init JobHandler Repository (for method)
        initJobHandlerMethodRepository(applicationContext);

        // refresh GlueFactory
        GlueFactory.refreshInstance(1);

        // super start
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

查看其继承实现关系,XxlJobSpringExecutor初始化的时候会调用afterSingletonsInstantiated()方法。

  • initJobHandlerRepository(applicationContext);
    旧版本中用来注册带有 @JobHandler 注解的bean的Java类, 2.2.1-SNAPSHOT版本及以上已经不支持这种方式;

  • initJobHandlerMethodRepository(applicationContext);
    利用MethodIntrospector工具类的selectMethods方法和MetadataLookup接口得到Map<Method, XxlJob>。

  • GlueFactory.refreshInstance(1);
    刷新GlueFactory为 SpringGlueFactory,在执行 glue 模式的任务时使用 spring 来加载相应实例。

  • super.start();
    调用XxlJobExecutor.start() 。

public void start() throws Exception {

        // init logpath
        XxlJobFileAppender.initLogPath(logPath);

        // init invoker, admin-client
        initAdminBizList(adminAddresses, accessToken);


        // init JobLogFileCleanThread
        JobLogFileCleanThread.getInstance().start(logRetentionDays);

        // init TriggerCallbackThread
        TriggerCallbackThread.getInstance().start();

        // init executor-server
        initEmbedServer(address, ip, port, appname, accessToken);
    }
  • XxlJobFileAppender.initLogPath(logPath);
    文件夹不存在的情况下,创建日志文件夹

  • initAdminBizList(adminAddresses, accessToken);
    初始化AdminBizClient集合,AdminBizClient提供callback(回调)、registry(注册)以及registryRemove(注册移除)到调度中心的方法。

    @Override
    public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
        return XxlJobRemotingUtil.postBody(addressUrl+"api/callback", accessToken, timeout, callbackParamList, String.class);
    }

    @Override
    public ReturnT<String> registry(RegistryParam registryParam) {
        return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class);
    }

    @Override
    public ReturnT<String> registryRemove(RegistryParam registryParam) {
        return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryParam, String.class);
    }
  • JobLogFileCleanThread.getInstance().start(logRetentionDays);
    初始化日志清除线程,过期日志自动清理(清理N天前的日志文件)。

  • TriggerCallbackThread.getInstance().start();
    执行结果回调相关

  • initEmbedServer(address, ip, port, appname, accessToken);
    初始化执行器IP 地址与端口,如果IP没配置则默认取当前服务的地址,如果端口没配置则默认为9999
    生成服务调用地址"http://{ip_port}/"
    调用embedServer.start(address, port, appname, accessToken)方法。

public void start(final String address, final int port, final String appname, final String accessToken) {
        executorBiz = new ExecutorBizImpl();
        thread = new Thread(new Runnable() {

            @Override
            public void run() {

                // param
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workerGroup = new NioEventLoopGroup();
                ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
                        0,
                        200,
                        60L,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(2000),
                        new ThreadFactory() {
                            @Override
                            public Thread newThread(Runnable r) {
                                return new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode());
                            }
                        },
                        new RejectedExecutionHandler() {
                            @Override
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
                            }
                        });


                try {
                    // start server
                    ServerBootstrap bootstrap = new ServerBootstrap();
                    bootstrap.group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel channel) throws Exception {
                                    channel.pipeline()
                                            .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
                                            .addLast(new HttpServerCodec())
                                            .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
                                            .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
                                }
                            })
                            .childOption(ChannelOption.SO_KEEPALIVE, true);

                    // bind
                    ChannelFuture future = bootstrap.bind(port).sync();

                    logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

                    // start registry
                    startRegistry(appname, address);

                    // wait util stop
                    future.channel().closeFuture().sync();

                } catch (InterruptedException e) {
                    if (e instanceof InterruptedException) {
                        logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
                    } else {
                        logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
                    }
                } finally {
                    // stop
                    try {
                        workerGroup.shutdownGracefully();
                        bossGroup.shutdownGracefully();
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }

            }

        });
        thread.setDaemon(true);	// daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
        thread.start();
    }

ExecutorBizImpl实现了ExecutorBiz,包含了任务的心跳检测、忙碌检测、触发任务、终止任务以及查看执行日志的方法。

  1. 使用netty_http启动http 服务,绑定你传入设置执行器的端口。通过 EmbedHttpServerHandler来处理调度中心调度执行器中的任务
  2. startRegistry注册当前执行器的地址到调度中心 调度中心对执行器的操作源码见:EmbedHttpServerHandler类(此类写在EmbedServer内)。

六、实际项目集成xxl-job

以上的操作与我们本身在开发的项目没有关系,现在大致有两种思路进行xxl-job的应用:

  1. 将执行器集成到正在开发或已经上线的Spring或SpringBoot项目上
  2. 直接单独创建SpringBoot或Spring(不推荐)项目,采用类似微服务的方式进行单独部署job服务

优缺点

  • 第一种:可以复用以前和数据库交互的service类或其他如redis、mq等功能,缺点是需要附着在原有项目上,可能会造成性能影响,且没有解耦
  • 第二种:完全解耦,但需要重新搭建数据库交互、redis、mq等,优点是独立出来方便管理,且解耦,不会对其他正在运行的服务造成任何影响。

6.1 Spring项目集成xxl-job

现在仅以第一种方式为例进行探讨,我们项目是Spring,Bean模式集成方法如下

6.1.1 步骤一

复制xxl.job.properties文件到配置文件夹
上下文引入该文件

<bean class="com.neusoft.system.handler.ConvertPropertyConfigurer">
		<property name="locations">
			<list>
				//*****
				<value>WEB-INF/conf/platform/common/xxl.job.properties</value>
				//*****
			</list>
		</property>
	</bean>

6.1.2 步骤二

配置XxlJobExecutor实例,我们Spring版本比较低,所以只能采用这种硬编码的方式,有多少job就new多少对应IJobHandler实现类。

@Bean(initMethod = "start", destroyMethod = "destroy")
    public XxlJobExecutor xxlJobExecutor() {
        XxlJobExecutor.registJobHandler("DemoJob1", new DemoJob1());//
        XxlJobExecutor.registJobHandler("DemoJob2", new DemoJob2());//
        //...
        
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
        xxlJobExecutor.setIp(ip);
        xxlJobExecutor.setPort(port);
        xxlJobExecutor.setAppname(appname);
        xxlJobExecutor.setAdminAddresses(addresses);
        xxlJobExecutor.setLogPath(logpath);
        xxlJobExecutor.setAccessToken(accessToken);
        return xxlJobExecutor;
    }

Spring版本高的可以采用如下方式,可实现注解自动扫描的效果。

 @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

6.1.3 步骤三

job改造

public class DemoJob extends IJobHandler
{
    private static Logger logger = LoggerFactory.getLogger(UserYearScoreJob.class);

    @Override
    public ReturnT<String> execute(String arg0) throws Exception {
        Calendar cal = Calendar.getInstance();
        Date curDt = cal.getTime();
        int curYear = cal.get(Calendar.YEAR);
        try {
            XxlJobLogger.log("DemoJob start...");
            logger.info("DemoJob start...");
            //handle(curDt, curYear);
            logger.info("DemoJob end...");
            XxlJobLogger.log("DemoJob end...");
        } catch (ParseException e) {
            e.printStackTrace();
            throw e;
        }
        return ReturnT.SUCCESS;
    }

Spring高版本可用如下方式

@Component
public class SimpleJobHandler {

    private static final Logger logger = LoggerFactory.getLogger(SimpleJobHandler.class);

    @Autowired
    private TaskJob taskJob;

    @XxlJob("demoJobHandler")
    public ReturnT<String> demoJobHandler(String param) throws Exception {
        XxlJobLogger.log("XXL-JOB, demoJobHandlerstart---------------");
        try {
            taskJob.execute(param);
            return ReturnT.SUCCESS;
        } catch (Exception exception) {
            XxlJobLogger.log("demoJobHandler发生异常:{}", exception.getMessage(), exception);
            if (exception instanceof InterruptedException) {
                throw exception;
            }
            return new ReturnT<>(ReturnT.FAIL_CODE, exception.getMessage());
        } finally {
            XxlJobLogger.log("XXL-JOB, demoJobHandlerend---------------");
        }
    }
}

至此,已完成实际Spring项目集成xxl-job执行器。

另外,SpringBoot集成类似,完成引入文件、配置类、job改造三个步骤即可完成集成。

需要注意的是,xxl-job的调度中心本身就是一个独立项目,没必要集成在其他项目中。

以上就是我们如何在项目中应用xxl-job的方法。

七、实际遇到的问题及发现的特性

1.调度中心和执行器引用的core包版本不一致
执行器无法完成回调,调度中心在日志中无法显示执行结果
2、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
其中单机串行,是单个任务在单台机器上的串行,与其他任务无关,与其他机器无关
3、任务失败重试:支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;其中分片任务支持分片粒度的失败重试;
这里有个坑,如果业务处理逻辑被try catch了则无法捕捉到失败,系统会认为是正常,所以如果有try的去掉try或在catch里抛出来即可。
4、任务失败告警;默认提供邮件方式失败告警,同时预留扩展接口,可方便的扩展短信、钉钉等告警方式;
失败预警可以实现JobAlarm,参照EmailJobAlarm编写如短信、微信等预警模式。
5、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
其中故障转移在每次调度的时候会有心跳检测,如果监测的第一台有问题则检测另外一台,直到命中正常的执行器。
其中轮询,是单个任务在集群执行器上轮询,与其他任务无关。
6、分片广播任务:执行器集群部署时,任务路由策略选择”分片广播”情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务。且每台执行器的分片参数index都不一样
可根据分片参数(index、total)开发分片任务,当只有一台机器时,index=0,total=1。
这种分片广播,即为并行执行,另外也可通过同一个jobHandler配置多次并传递参数实现分片,但是其路由无规律,且容易丢失,不建议使用。
注意:分片每次索引一致,且与注册先后无关,按照排序与索引号一一对应,总数不变的情况下,每次分片索引和机器对应关系不变。执行器和调度中心存在心跳,集群中一台机器宕机,不会立马剔除,会导致分片数据未及时更新。
7、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。
这种方式更加灵活,可应对临时突发情况,及时修改代码。
8、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
在失败重试时,粒度可到子任务的失败重试。
9.停止任务,日志页面终止任务,调度中心会通知执行器,及时抛出interrupted异常;任务页面 停止调度,不影响本轮次已经触发的任务,且可以正常回调结果。

以上,仅供参考。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值