一次性入门三款分布式定时任务调度框架:Quartz、ElasticJob3.0、xxl-job

分布式定时任务调度框架(文末有源码)

  • 前言
  • 1、Quartz
    • [1.1 数据库](#1.1 数据库)
    • [1.2 maven依赖](#1.2 maven依赖)
    • [1.3 代码实现](#1.3 代码实现)
      • [1.3.1 创建一个job](#1.3.1 创建一个job)
      • [1.3.1 为job设置trigger](#1.3.1 为job设置trigger)
    • [1.4 配置文件](#1.4 配置文件)
    • [1.5 启动、测试](#1.5 启动、测试)
      • [1.1 单机](#1.1 单机)
      • [1.2 集群](#1.2 集群)
  • 2、ElasticJob
    • [2.1 下载zk](#2.1 下载zk)
    • [2.2 新建三个类型的作业](#2.2 新建三个类型的作业)
    • [2.3 配置文件](#2.3 配置文件)
    • [2.4 启动项目,测试数据](#2.4 启动项目,测试数据)
    • [2.5 分片作业(集群测试)](#2.5 分片作业(集群测试))
      • [2.5.1 **官方的三个分片策略:**](#2.5.1 官方的三个分片策略:)
      • [2.5.2 启动三个实例,模拟集群](#2.5.2 启动三个实例,模拟集群)
      • [2.5.3 模拟集群故障](#2.5.3 模拟集群故障)
    • [2.6 数据流作业](#2.6 数据流作业)
    • [2.7 控制台console](#2.7 控制台console)
  • 3、xxl-job
    • 1.搭建调度中心(必须要搭)
      • [1.1 创建好数据库](#1.1 创建好数据库)
      • [1.2 修改application.properties文件](#1.2 修改application.properties文件)
      • [1.3 修改logback.xml文件](#1.3 修改logback.xml文件)
      • [1.4 启动项目](#1.4 启动项目)
      • [1.5 打包部署](#1.5 打包部署)
    • 2.springboot项目中集成
      • [2.1 maven依赖](#2.1 maven依赖)
      • [2.2 配置文件](#2.2 配置文件)
      • [2.3 注入config](#2.3 注入config)
      • [2.4 新增job](#2.4 新增job)
      • [2.5 简单归纳一下](#2.5 简单归纳一下)
    • 3.结合控制台使用
      • [3.1 配置执行管理器](#3.1 配置执行管理器)
      • [3.2 新增一个任务](#3.2 新增一个任务)
      • [3.3 启动](#3.3 启动)
    • 4.用分片任务,来验证xxl-job的集群功能
      • [4.1 修改端口](#4.1 修改端口)
      • [4.2 新建执行器](#4.2 新建执行器)
      • [4.3 新建任务代码+任务](#4.3 新建任务代码+任务)
      • [4.4 运行一次验证](#4.4 运行一次验证)
    • 5.对Quartz的优化
  • 4、结语
  • 5、附录1
  • 6、附录2

前言

本来是打算水三篇文章的,但是最后想想还是打包一起,这样后来自己看也方便一点,所以本文略长,大家可以按需目录跳转阅读。每个框架都包含了入门的代码示例,文末会贴上源码链接,大家可以下载阅读。

本文主要是为了示例,包含了 单机和集群 的代码,一些基本的概念知识就不说了,大家可以去官网详细阅读。

1、Quartz

最经典的一款框架,下面两个框架都是基于Quartz改进的。本文版本用的最新版本:2.3.0

核心理念

  • scheduler:调度器
  • job:任务本身
  • trigger:触发器

1.1 数据库

Quartz是基于数据库来做任务调度的,所以我们要先把数据库构造好,用官网给的SQL脚本。

由于脚本太长了,放在这里占位置影响阅读,附录1有完整脚本,直接copy

如果大家有源码的,在src\org\quartz\impl\jdbcjobstore这个目录下面,也可以解压maven依赖,一样可以获取到SQL脚本

创建好的就这么11张表

1.2 maven依赖

bash 复制代码
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-quartz</artifactId>
 </dependency>
 <dependency>
     <groupId>com.mchange</groupId>
     <artifactId>c3p0</artifactId>
     <version>0.10.0</version>
 </dependency>

我这里引入c3p0是因为会报连接错误,但是网上其他文章不用引入这个依赖,我猜测可能是版本不一样,然后某个Maven依赖冲突导致的。

1.3 代码实现

创建job的方式有两种,一种是比较古老的,用scheduler去绑定job和config,由于配置复杂,不是很推荐,推荐用spring bean的自动装配。

1.3.1 创建一个job

@DisallowConcurrentExecution注解是关键,用于集群中单个job只能被一个实例机器执行。

java 复制代码
//禁止并发执行
@DisallowConcurrentExecution
//更新JobDataMap 副本
@PersistJobDataAfterExecution
public class ZedJob extends QuartzJobBean {
    private static final Logger logger = LoggerFactory.getLogger(ZedJob.class);

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        logger.info("我是ZedJob");
    }
}

1.3.1 为job设置trigger

java 复制代码
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail jobDetail(){
        return JobBuilder.newJob(ZedJob.class)
                // 指定任务的名称
                .withIdentity("zedJob")
                // 任务描述
                .withDescription("任务描述:这是一个任务")
                // 每次任务执行后进行存储
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger() {
        //创建触发器
        SimpleScheduleBuilder simpleScheduleBuilder1 = SimpleScheduleBuilder.repeatSecondlyForever(5);
        return TriggerBuilder.newTrigger()
                // 绑定工作任务
                .withIdentity("zedJob")
                .forJob(jobDetail())
                // 每隔 5 秒执行一次 job
                .withSchedule(simpleScheduleBuilder1)
                .build();
    }
}

这里的Schedule常用的有两种,一个是SimpleScheduleBuilder,一个是CronScheduleBuilder,写cron表达式的,大家可以按照需要选取。

1.4 配置文件

上面基本都有注释

yaml 复制代码
spring:
  quartz:
    #存储方式,默认是内存memory
    job-store: jdbc
    #应用关闭时,是否等待定时任务执行完成
    wait-for-jobs-to-complete-on-shutdown: true
    properties:
      org:
        quartz:
          scheduler:
            #相同 Scheduler 名字的节点,形成一个 Quartz 集群
            instanceName: SC_Scheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
#            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            dataSource: quartz_jobs
            #是否集群部署
            isClustered: true
            # 集群检查周期,单位为毫秒,可以自定义缩短时间。当某一个节点宕机的时候,其他节点等待多久后开始执行任务
            clusterCheckinInterval: 5000
          threadPool:
            threadCount: 25 # 线程池大小。默认为 10 。
            threadPriority: 5 # 线程优先级
            class: org.quartz.simpl.SimpleThreadPool # 线程池类型
          dataSource:
            #这个名字是上面的jobStore.dataSource定义的
            quartz_jobs:
              driver: com.mysql.cj.jdbc.Driver
              URL: jdbc:mysql://172.16.72.134:3306/quartz_jobs?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2b8&useUnicode=true
              user: root
              password: 123456

我这里挑几个比较重要的来说一下:

  • job-store:指定存储方式,如果要部署分布式集群,必须要选择jdbc
  • instanceName:同一个集群,instanceName要一样,quartz在数据库中是用名字来区分是否是一个集群的。
  • isClustered:这是集群的开启开关
  • dataSource:选择数据库集群后,才会有这个配置
  • quartz_jobs:命名记得一致

1.5 启动、测试

1.1 单机

启动单个服务,看看打印,都是正常的。

1.2 集群

只修改端口,启动两个实例,测试@DisallowConcurrentExecution 并发执行,记得数据库这些配置信息要一致

可以看到,我们第二个实例中,没有任何打印

我们关闭第一个实例,假装宕机,看看第二个实例能否正常接续工作

当关闭1后,我们可以看到,示例2中检测到了1的关闭,并且有打印三句话,这三句话的意思翻译一下:

  1. 检测到集群中有一个实例失去连接或者在重启
  2. 扫描是否有其他示例可以接待挂掉的那个主机继续执行任务
  3. 找到了1个实例,去继续执行

所以由此也可以看出quartz集群的能力和容错能力。

2、ElasticJob

ElasticJob把任务命名为作业,等同于任务,job,叫法不同而已,所以下面都用作业来说

2.1 下载zk

  1. 去官网下载最新版本:下载链接

  2. 解压,进入conf 路径下,将文件 zoo_sample.cfg 修改为 zoo.cfg

    bash 复制代码
    mv zoo_sample.cfg zoo.cfg
  3. 在zk根目录下创建目录zkData

    bash 复制代码
    mkdir zkData
  4. 打开 zoo.cfg 文件,修改 dataDir 路径为新创建的zkData路径

    bash 复制代码
    dataDir=/usr/local/zookeeper/apache-zookeeper-3.9.2-bin/zkData
  5. 进入bin目录,启动zk

    bash 复制代码
    ./zkServer.sh start
  6. 运行客户端,创建命名空间

    bash 复制代码
    ./zkCli.sh
    bash 复制代码
    create /elastic-job-test

2.2 新建三个类型的作业

ZedJob:普通的作业

FragmentationJob:分片作业

FlowJob:数据流作业

java 复制代码
@Component
public class ZedJob implements SimpleJob {
    private static final Logger logger = LoggerFactory.getLogger(ZedJob.class);

    @Override
    public void execute(ShardingContext shardingContext) {
        logger.info("我是影流之主 Zed");
    }
}
java 复制代码
@Component
public class FragmentationJob implements SimpleJob {
    private static final Logger logger = LoggerFactory.getLogger(FragmentationJob.class);

    @Override
    public void execute(ShardingContext shardingContext) {
        logger.info("分片任务");
        switch (shardingContext.getShardingItem()) {
            case 0:
                logger.info("分片0:{}",shardingContext.getShardingParameter());
                break;
            case 1:
                logger.info("分片1:{}",shardingContext.getShardingParameter());
                break;
            case 2:
                logger.info("分片2:{}",shardingContext.getShardingParameter());
                break;
            default:
                break;
        }
    }
}
java 复制代码
@Component
public class FlowJob implements DataflowJob<UserInfo> {
    private static final Logger logger = LoggerFactory.getLogger(FlowJob.class);

    @Override
    public List<UserInfo> fetchData(ShardingContext shardingContext) {
        List<UserInfo> foos = new ArrayList<>();
        double random = Math.random();
        if (random > 0.5) {
            logger.info("fetchData------ {}", random);
            UserInfo foo = new UserInfo();
            foo.setUserName("小道仙");
            foos.add(foo);
        }
        return foos;
    }

    @Override
    public void processData(ShardingContext shardingContext, List<UserInfo> list) {
        logger.info("收到流数据");
        list.forEach(i -> logger.info("name为:{}", i.getUserName()));
    }
}

2.3 配置文件

大多都有注释,大家看看就行,其中shardingTotalCount必填,等待重试的间隔也很重要。

yaml 复制代码
elasticjob:
  regCenter:
    #zookeeper 的ip:port
    serverLists: 172.16.72.133:2181
    #名命空间,和前面zk里面创建的一样就行
    namespace: elastic-job-test
    base-sleep-time-milliseconds: 10000
    # 等待重试的间隔时间的最大值,这个很重要,不然项目启动会出现timeout的报错
    max-sleep-time-milliseconds: 30000
  jobs:
    #我们创建的三个任务,在这里进行配置
    zedJob:
      #定时任务的全路径名
      elasticJobClass: com.wq.elasticjob.ZedJob
      #定时任务执行的cron表达式
      cron: 0/5 * * * * ?
      #分片数量
      shardingTotalCount: 1
    fragmentationJob:
      #定时任务的全路径名
      elasticJobClass: com.wq.elasticjob.FragmentationJob
      #定时任务执行的cron表达式
      cron: 0/10 * * * * ?
      #分片数量
      shardingTotalCount: 3
      #这是分片参数,代码中获取,记得加引号,不然获取不到
      shardingItemParameters: "0=text,1=image,2=video"
    flowJob:
      #定时任务的全路径名
      elasticJobClass: com.wq.elasticjob.FlowJob
      #定时任务执行的cron表达式
      cron: 0/10 * * * * ?
      #分片数量
      shardingTotalCount: 1

2.4 启动项目,测试数据

我们先看看普通的作业,这个没可说的,看看能否正常运行就好

接下来重点说说分片任务,以及流数据任务。

2.5 分片作业(集群测试)

2.5.1 官方的三个分片策略:

AverageAllocationJobShardingStrategy:根据分片项平均分片

如果分片不能整除,则不能整除的多余分片将依次追加到序号小的服务器。策略举例:

假设有3台服务器,

分成9片,则每台服务器分到的分片是:1【0,1,2】,2【3,4,5】,3【6,7,8】

分成8片,则每台服务器分到的分片是:1【0,1,6】,2【2,3,7】,3【4,5】

分成10片,则每台服务器分到的分片是:1【0,1,2】,2【3,4,5】,3【6,7,8】

OdevitySortByNameJobShardingStrategy:根据作业名称哈希值的奇偶数决定按照作业服务器 IP 升序或是降序的方式分片

缺点是,一旦分片数小于作业服务器数,作业将永远分配至IP地址靠前的服务器,导致IP地址靠后的服务器空闲

RotateServerByNameJobShardingStrategy:根据作业名称轮询分片

假设有3台服务器,顺序为 【0, 1, 2】,如果作业名的哈希值根据作业分片总数取模为 1, 作业节点顺序变为 【1, 2, 0】。

2.5.2 启动三个实例,模拟集群



回顾一下,我们分片任务的配置:shardingItemParameters: "0=text,1=image,2=video",总数有三个,启动三个实例,刚好一个机器一个,而且参数也能打印出来,而且分片策略,默认采用的是平均分片。

2.5.3 模拟集群故障

现在我把三个集群中index为0的干掉,看看效果。

通过打印可以看到,本来三个集群一人一个,现在挂掉了一个,其中一个服务还是一个,另一个服务则是拥有了两个分片,到这里也就验证结束

2.6 数据流作业

回到我们流数据任务的代码:

java 复制代码
    @Override
    public List<UserInfo> fetchData(ShardingContext shardingContext) {
        List<UserInfo> foos = new ArrayList<>();
        double random = Math.random();
        if (random > 0.5) {
            logger.info("fetchData------ {}", random);
            UserInfo foo = new UserInfo();
            foo.setUserName("小道仙");
            foos.add(foo);
        }
        return foos;
    }

    @Override
    public void processData(ShardingContext shardingContext, List<UserInfo> list) {
        logger.info("收到流数据");
        list.forEach(i -> logger.info("name为:{}", i.getUserName()));
    }

我们实现的DataflowJob接口,覆盖了两个方法,一个fetchData,一个processData

  • fetchData:对数据作处理
  • processData:如果fetchData方法中产生了数据(数据发生了改变),则会携带者数据进入这个方法

我们看看日志打印,当我们在fetchData中设置了userName后,就会进入processData方法,就会拿到我们设置的userName属性

2.7 控制台console

  1. 选择对应版本下载
    控制台官网下载:https://www.apache.org/dyn/closer.cgi/shardingsphere/elasticjob-ui-3.0.0-RC1/apache-shardingsphere-elasticjob-3.0.0-RC1-lite-ui-bin.tar.gz
  2. 解压后进入bin目录,选择start.bat双击启动
  3. 网页访问localhost:8088/,用户名/密码:root/root
  4. 按照下图三个步骤配置zk注册中心,然后就能使用了

3、xxl-job

截止本文编写时候,xxl的最新Maven版本为2.4.1,所以我们就用最新的

我们把xxl-job看做两部分,一部分是搭建调度中心,一部分是代码中实现集成

1.搭建调度中心(必须要搭)

下载源码,仓库地址:

gitee地址:https://gitee.com/xuxueli0323/xxl-job/blob/master/doc/db/tables_xxl_job.sql

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

1.1 创建好数据库

脚本在这个目录下面:doc/db/tables_xxl_job.sql

调度中心支持集群部署,集群情况下各节点务必连接同一个mysql实例;
如果mysql做主从,调度中心集群节点务必强制走主库;

这里给出所有数据库的作用:

  • xxl_job_lock:任务调度锁表;
  • xxl_job_group:执行器信息表,维护任务执行器信息;
  • xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
  • xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
  • xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;
  • xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
  • xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
  • xxl_job_user:系统用户表;

不想去找的直接文末附录二有完整的SQL脚本,直接copy

1.2 修改application.properties文件

修改server.port为自己需要的

修改spring.datasource.url,数据库配置为自己的

修改spring.mail.username和from为自己的实际邮箱地址(不修改也可以,这个就是通知用的)

1.3 修改logback.xml文件

将name="log.path" 这个路径修改为自己的路径,并且在路径下创建xxl-job-admin.log文件,如果你不修改用默认的也行。

1.4 启动项目

浏览器访问:localhost:8088/xxl-job-admin/,默认用户名密码为admin,123456

出现下面这个界面则搭建成功

1.5 打包部署

生产环境下,配置好以后,打包部署到服务器即可

2.springboot项目中集成

2.1 maven依赖

bash 复制代码
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.4.1</version>
</dependency>

2.2 配置文件

yaml 复制代码
xxl:
  job:
    admin:
      #如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册
      addresses: http://127.0.0.1:8088/xxl-job-admin
    executor:
      appname: zed-demo
      logpath:
      logretentiondays: 30
      #执行器IP:默认为空表示自动获取IP
      ip: 127.0.0.1
      #默认为9999
      port: 9999
      #优先使用该配置作为注册地址,为空时使用内嵌服务 "IP:PORT" 作为注册地址
      address:
    #执行器通讯TOKEN [选填]:非空时启用; 要和调度中心服务部署配置的accessToken一致
    accessToken: default_token

2.3 注入config

这个config是官方源码,内容一样,但是必须要copy进我们自己的项目里

java 复制代码
@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

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

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

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

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

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

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

    @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.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

    /**
     * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
     *
     *      1、引入依赖:
     *          <dependency>
     *             <groupId>org.springframework.cloud</groupId>
     *             <artifactId>spring-cloud-commons</artifactId>
     *             <version>${version}</version>
     *         </dependency>
     *
     *      2、配置文件,或者容器启动变量
     *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
     *
     *      3、获取IP
     *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
     */

}

2.4 新增job

这里用最常用最推荐的一种写法,就是将任务绑定在方法上,用@XxlJob注解标明每一个任务。当然还有绑定在Bean上的,不过这样就会写很多Bean,用方法的话,一个类就够了。

java 复制代码
@Component
public class XxlJobFactory {
    private static Logger logger = LoggerFactory.getLogger(XxlJobFactory.class);

    @XxlJob("zedJobTest")
    public void zedJobTest(){
        logger.info("这是zedJobTest");
    }
}

2.5 简单归纳一下

我们在springBoot项目集成的时候,有几个比较关键的点,我在这里给大家总结一下,免得绕进去。

  1. 第一步搭建的调度中心,你可以理解为es job 的zookeeper一样,或者nacos这样的角色,它是一个注册中心,是整个框架的驱动核心,采用db的方式来进行服务的注册。
  2. xxl.job.admin:这个在springboot中的配置,就是配置第一步的调度中心的,地址前面一定要加http
  3. xxl.job.executor:这个是接下来要说的执行管理器,去执行任务的executor,它配置在我们的网页控制台中。这个的ip和端口是单独分开的,专门控制executor
  4. 很多配置都是空的,因为都有默认的,但是我们在注入XxlJobConfig的时候必须要用,所以空着就空着

3.结合控制台使用

3.1 配置执行管理器

新增一个执行管理器,这就是我们前面说到的,xxl.job.executor配置所控制的东西,它有一个默认的,我们不用默认的用自己的。

这个appName一定要和xxl.job.executor里面的appname一样,一般默认自动注册就好,如果自动注册不行,就手动录入,输入我们的ip和端口,这个ip和端口,也一定要和xxl.job.executor.port一致

3.2 新增一个任务

这里面最主要注意三个点,就是我标出来的这三个

  1. 执行器选择我们第二步中创建的执行器
  2. 运行模式常用Bean
  3. 然后jobHander要和代码中,注解@XxlJob中的Value一致

其他的根据自己需求改写就行

3.3 启动

这里选择执行一次,忽略corn表达式,只执行一次,选择下面的启动,才会走corn表达式逻辑。

执行一次这里,可以加任务参数和重新指定机器地址,由于我们执行器里面配置好了的,所以不用填写让它自动获取。

看看控制台打印,任务是调动成功了的。

然后数据库里,也可以看到我们新增的任务,还有其他表的信息

4.用分片任务,来验证xxl-job的集群功能

4.1 修改端口

修改springboot的端口和executor的port,启动三个实例

4.2 新建执行器

新建一个执行器fragmentatio

这里自动注册如果获取不到,我们就手动录入,三个不同的端口之间用逗号分隔。外面查看也是正确的。

4.3 新建任务代码+任务

java 复制代码
    /**
     * 2、分片广播任务
     */
    @XxlJob("shardingJobHandler")
    public void shardingJobHandler(String param) throws Exception {
        int shardTotal = XxlJobHelper.getShardTotal();
        int index = XxlJobHelper.getShardIndex();
        logger.info("总共:{}个分片,当前的index为:{}", shardTotal, index);
    }

记得路由策略选择分片广播

4.4 运行一次验证

我们看看控制台打印,我这里贴一个就行了,三个实例,总共3个分片,index为0,1,2,大家可以根据分片总数和index,自己去实现分片处理逻辑,比如取模。

5.对Quartz的优化

xxl-job,是徐雪里基于Quartz改进来的,Quartz有下面几点不足:

  1. 调用API的的方式操作任务,不人性化;
  2. 需要持久化业务QuartzJobBean到底层数据表中,系统侵入性相当严重。
  3. 调度逻辑和QuartzJobBean耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;
  4. quartz底层以"抢占式"获取DB锁并由抢占成功节点负责运行任务,会导致节点负载悬殊非常大;而XXL-JOB通过执行器实现"协同分配式"运行任务,充分发挥集群优势,负载各节点均衡。

XXL-JOB弥补了quartz的上述不足之处

4、结语

都看到这里了,给我点个赞和关注不过分吧,如果你只是使用分布式任务调度框架,本文的示例绝对够用,如果你们项目很庞大而且很复杂,那么还要去官网查看更详细的配置。

本文完整项目代码GitHub地址

https:https://github.com/wangqing-github/DubboAndNacos.git

ssh:git@github.com:wangqing-github/DubboAndNacos.git

有三个分支,代码都是独立的,大家可以从主干切换。

5、附录1

Quartz的数据库SQL脚本:

sql 复制代码
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;


CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);


commit;

6、附录2

xxl的sql脚本

sql 复制代码
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
use `xxl_job`;

SET NAMES utf8mb4;

CREATE TABLE `xxl_job_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `job_group` int(11) NOT NULL COMMENT '执行器主键ID',
  `job_desc` varchar(255) NOT NULL,
  `add_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `author` varchar(64) DEFAULT NULL COMMENT '作者',
  `alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
  `schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
  `schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
  `misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
  `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
  `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
  `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
  `executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
  `executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
  `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
  `glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
  `glue_source` mediumtext COMMENT 'GLUE源代码',
  `glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
  `glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
  `child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
  `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
  `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
  `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `job_group` int(11) NOT NULL COMMENT '执行器主键ID',
  `job_id` int(11) NOT NULL COMMENT '任务,主键ID',
  `executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
  `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
  `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
  `executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
  `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
  `trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
  `trigger_code` int(11) NOT NULL COMMENT '调度-结果',
  `trigger_msg` text COMMENT '调度-日志',
  `handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
  `handle_code` int(11) NOT NULL COMMENT '执行-状态',
  `handle_msg` text COMMENT '执行-日志',
  `alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
  PRIMARY KEY (`id`),
  KEY `I_trigger_time` (`trigger_time`),
  KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_log_report` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
  `running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
  `suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
  `fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_logglue` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `job_id` int(11) NOT NULL COMMENT '任务,主键ID',
  `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
  `glue_source` mediumtext COMMENT 'GLUE源代码',
  `glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
  `add_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_registry` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `registry_group` varchar(50) NOT NULL,
  `registry_key` varchar(255) NOT NULL,
  `registry_value` varchar(255) NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_group` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
  `title` varchar(12) NOT NULL COMMENT '执行器名称',
  `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
  `address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '账号',
  `password` varchar(50) NOT NULL COMMENT '密码',
  `role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
  `permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_lock` (
  `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
  PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31' );
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');

commit;
相关推荐
J不A秃V头A15 分钟前
IDEA实用小技巧:方法之间的优雅分割线
java·intellij-idea
涛涛6号25 分钟前
PageHelper(springboot,mybatis)
java·spring boot·后端
夜雨翦春韭37 分钟前
【代码随想录Day58】图论Part09
java·开发语言·数据结构·算法·leetcode·图论
豪宇刘1 小时前
Shiro回话管理和加密
java·后端·spring
V+zmm101341 小时前
警务辅助人员管理系统小程序ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·课程设计·1024程序员节
Seven 7 Chihiro1 小时前
[进阶]java基础之集合(三)数据结构
java·开发语言
小爬虫程序猿2 小时前
Java爬虫的京东“寻宝记”:揭秘商品类目信息
java·开发语言
耀耀_很无聊2 小时前
第十一部分 Java 数据结构及集合
java·开发语言·数据结构
webfunny20202 小时前
IDEA集成AI的DevAssist插件使用指南
java·ide·intellij-idea
柯基的小屁墩2 小时前
mac|maven项目在idea中连接redis
java·ide·intellij-idea