起因

最近公司准备使用xxl-job,让本人调研一下,以下是调研一下午的成果,记录一下

分布式任务调度调研(建议跳过)

开源方案

  • elastic-job:Elastic-Job是一个分布式调度解决方案,由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成。

    Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务。

    Elastic-Job-Cloud使用Mesos + Docker的解决方案,额外提供资源治理、应用分发以及进程隔离等服务。

    可惜的是已经两年没有迭代更新记录。

  • xxl-job:一个轻量级分布式任务调度平台,主打特点是平台化,易部署,开发迅速、学习简单、轻量级、易扩展,代码仍在持续更新中。

    “调度中心”是任务调度控制台,平台自身并不承担业务逻辑,只是负责任务的统一管理和调度执行,并且提供任务管理平台, “执行器” 负责接收“调度中心”的调度并执行,可直接部署执行器,也可以将执行器集成到现有业务项目中。 通过将任务的调度控制和任务的执行解耦,业务使用只需要关注业务逻辑的开发。

    ​ 主要提供了任务的动态配置管理、任务监控和统计报表以及调度日志几大功能模块,支持多种运行模式和路由策略,可基于对应执行器机器集群数量进行简单分片数据处理。

  • quartz:是Java领域最著名的开源任务调度工具,也是目前事实上的定时任务标准,几乎全部的开源定时任务框架都是基于Quartz核心调度构建而成。

    缺点:

    (1)需要把任务信息持久化到业务数据表,和业务有耦合。

    (2)调度逻辑和执行逻辑并存于同一个项目中,在机器性能固定的情况下,业务和调度之间不可避免地会相互影响。

    (3)quartz集群模式下,是通过数据库独占锁来唯一获取任务,任务执行并没有实现完善的负载均衡机制。

  • TBSchedule:阿里早期开源的分布式任务调度系统。代码略陈旧,使用的是Timer而不是线程池执行任务调度。TBSchedule的作业类型比较单一,只能是获取/处理数据一种模式,文档缺失比较严重。

  • Saturn:唯品会开源的一个分布式任务调度平台,在Elastic Job的基础上进行了改造。

  • CronMan:

技术选型

XXL-JOB:

  1. 轻量级,支持通过Web页面对任务进行动态CRUD操作,操作简单
  2. 只依赖数据库作为集群注册中心,接入开发简单,不需要ZK
  3. 高可用、解耦、高性能、监控报警、分片、重试、故障转移
  4. 团队持续开发,社区活跃
  5. 支持后台直接查看每个任务执行实时日志,ELASTIC-JOB中应该是没有这个功能

具体实现

先从git或者gitee上clone下项目
1、配置nginx

CentOS 7 下安装 Nginx

2、配置db

运行项目中:doc-db-sql脚本

3、配置xxl-job调度中心

修改admin模块配置:
去掉server.servlet.context-path这行
在这里插入图片描述

在这里插入图片描述

将admin工程打成jar,上传到服务器,运行

nohup java -jar xxl-job-admin-2.3.0.jar &

4、项目中集成xxl-job执行器(也可单独部署执行器,并使用rpc调用任务)

导入依赖:

<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.0</version>
</dependency>

配置执行器:注意server.port 和 xxl.job.admin.addresses 和 xxl.job.executor.appname 和 xxl.job.executor.port

# web port
server.port=8082
# no web
#spring.main.web-environment=false

# log config
logging.config=classpath:logback.xml


### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://192.168.216.133:8080/xxl-job-admin/

### xxl-job, access token
xxl.job.accessToken=

### xxl-job executor appname
xxl.job.executor.appname=test-executor
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30

编写config类(clone下来的项目中此处有坑,应该是作者没改,需将@Bean后括号中内容删掉,不然启动报错):

package cn.xqh.xxljobtest.jobhandler.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author: qihang.xu
 * @date: 2021/12/15 17:03
 * @desc: TODO
 */
@Configuration
@ComponentScan(basePackages = "cn.xqh.xxljobtest.jobhandler")
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.executor.appname}")
    private String appName;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

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

}

编写handler:

package cn.xqh.xxljobtest.jobhandler;

import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

/**
 * @author: qihang.xu
 * @date: 2021/12/15 16:57
 * @desc: TODO
 */
@Component
public class TestJobHandler extends IJobHandler {

    @Override
    @XxlJob(value = "AJobHandler")
    public void execute() throws Exception {
        System.out.println("定时任务执行");
    }
}

5、调度中心配置执行器和执行任务
调度中心->执行器管理->新增,名称和你执行器配置的名称相同
在这里插入图片描述调度中心->任务管理->新增
在这里插入图片描述

6、项目定时任务迁移测试:执行成功!
在这里插入图片描述

高可用实践

1、执行器高可用

在项目中做完上面第4步后,打包项目,修改执行器端口,再次打包项目;发布项目,现在就有了两个除执行器配置不同,其余逻辑都相同的jar,部署jar,在调度中心-执行器管理中,可以看到执行器集群:
在这里插入图片描述

再在任务管理中配置执行器策略:轮询、故障转移等;
在这里插入图片描述验证:启动任务后,停掉一个执行器服务,可以看到任务继续执行。懒得截图
2、调度中心高可用

单机:修改调度中心端口号,重新打包

非单机:直接打包

发布项目

通过nginx做负载均衡
在这里插入图片描述
配置完成
验证:访问服务器ip,可以跳到监控页面,停掉一个调度中心,访问页面正常。懒得截图ovo

遗留的问题

  • 在项目中集成执行器过于僵硬,将来升级的方向可以将执行器单独部署,在handler中通过rpc执行任务
  • nginx负载均衡,upstream中只能代理到端口,如果想在端口后继续增加路径,则报无法识别的错误,暂时未找到解决方案,只能去掉“server.servlet.context-path=”参数。(可能是本人nginx玩的不6,毕竟这是运维的事儿,能用就行,能用就行~)
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐