Skip to content

XXL-JOB 使用介绍

本文介绍XXL-JOB的基本使用与CRON表达式。

1. 介绍与优势

定时任务是指在特定时间或以特定间隔自动执行的程序或脚本。例如:

  • 某电商平台需要在每周一上午10点发放1000张优惠券;
  • 某财务系统需要在每天凌晨01:00:00结算前一天的财务数据,进行统计汇总;

在微服务环境下,如果存在多个服务实例,那么定时任务就会执行多次,造成问题。例如,如果该电商平台优惠券服务部署多实例,那么就会造成发放的优惠券不止1000张。

XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。

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

分布式任务调度平台可以解决如下问题:

  • 防止重复执行:如果有多实例同时运行,会造成定时任务重复执行出现问题,分布式任务调度平台保证每个时间点只有一个实例在执行任务;
  • 高可用:单机版的定时任务调度只能在一台机器上运行,如果程序或系统出现问题,就会导致功能不可用;分布式任务调度,可以保证高可用;
  • 提升效率:如果现在有大量任务需要处理。分布式任务调度可以并行处理任务,提升效率;

XXL-JOB的架构图如下,可以发现,XXL-JOB主要分为两部分:

  • 调度中心

    负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。 调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块; 支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。

  • 执行器

    负责接收调度请求并执行任务逻辑。 任务模块专注于任务的执行等操作,开发和维护更加简单和高效; 接收“调度中心”的执行请求、终止请求和日志请求等。

输入图片说明

2. 入门案例

2.1 环境准备

本小节介绍XXL-JOB的入门案例,首先下载源码:https://github.com/xuxueli/xxl-job/releases,下载完成后解压并用IDEA打开,项目结构如下:

image-20250802121540105

  • xxl-job-admin:调度中心源码,可以启动该服务,即启动调动中心;
  • xxl-job-core:执行器源码,可以在自己项目中引入该模块,即可以引入执行器;
  • xxl-job-executor-samples:案例代码;

2.2 调度中心的部署

首先,运行XXL-JOB项目下的doc/db/tables_xxl-job.sql脚本文件:

image-20250802122301061

然后,打开xxl-job-admin服务配置文件application.properties,主要关注以下配置:

properties
# 调度中心启动端口
server.port=28080

# 数据库配置
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
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# xxl-job, access token, 主要用于和执行器通信
xxl.job.accessToken=default_token

在启动项目时,有关日志路径可能会报文件不存在,需要修改日志路径,resources/logback.xml文件中找到如下一行:

xml
<property name="log.path" value="./data/applogs/xxl-job/xxl-job-admin.log"/>

将日志路径修改一下就可以了。

之后启动服务,在浏览器中访问路径:http://localhost:28080/xxl-job-admin,显示登录界面,默认的账号密码为admin/123456

image-20250802123305234

登录成功后即可进入调度中心主界面:

image-20250802123343790

至此,调度中心成功启动。

2.3 执行器的部署

所谓执行器,就是我们自己的项目,首先在项目中引入依赖:

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

然后在配置文件中写入以下有关XXL-JOB的内容:

properties
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:28080/xxl-job-admin
### 调度中心通讯TOKEN [选填]:非空时启用;
xxl.job.admin.accessToken=default_token
### 调度中心通讯超时时间[选填],单位秒;默认3s;
xxl.job.admin.timeout=3
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=my-xxl-job-executor
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯使用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=127.0.0.1
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=./data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30

向容器中注入XXL-JOB配置类:

java
@Configuration
public class XxlJobConfig {

    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

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

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

    @Value("${xxl.job.admin.timeout}")
    private int timeout;

    @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.setTimeout(timeout);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
}

最后创建任务类:

java
@Component
public class HelloJob {

    private static final Logger logger = Logger.getLogger(HelloJob.class.getName());

    @XxlJob(value = "helloJob")
    public void execute() {
        logger.info("hello job");
    }

}

使用注解@XxlJob指定任务方法和任务名。

启动执行器项目,然后在调度中心的执行器管理界面,查看注册的执行器:

image-20250802134349691

WARNING

注意,在执行器的配置文件中,如果修改了appName(xxl.job.executor.appname=my-xxl-job-executor),则需要在调度中心新建一个执行器分组,否则看不到。

2.4 任务的执行

当注册上执行器后,我们就可以到调度中心-任务管理界面,新建任务:

image-20250802134727683

  • 选择执行器为自己创建的执行器;
  • 运行模式选BeanJobHandler填写执行器中的任务名称;
  • Cron表达式设置为5秒执行一次;

保存后,启动任务。

在执行器控制界面,查看任务是否成功执行:

image-20250802134939804

至此,入门案例成功完成。

3. 其他配置

在创建任务时,还有许多配置可以修改,这一小节介绍这些配置。

3.1 任务参数

在配置任务时,可以设置任务参数:

image-20250802135930040

推荐使用JSON格式填写任务参数。

然后,在任务中通过XxlJobHelper获取任务参数:

java
@XxlJob(value = "helloJob")
public void execute() {
    String jobParam = XxlJobHelper.getJobParam();
    logger.info("jobParam:" + jobParam);
    logger.info("hello job");
}

结果如下:

image-20250802140113859

可以发现能正确获取到配置的任务参数。

3.2 GLUE模式

在配置任务时,除了BEAN模式,还有GLUE模式。

GLUE模式是指任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。

首先,在执行器项目中新增一个任务类:

java
@Component
public class GLUEJob {

    private static final Logger logger = Logger.getLogger(GLUEJob.class.getName());

    public void method1(){
        logger.info("method1");
    }

    public void method2(){
        logger.info("method2");
    }

}

注意,该任务类只是一个简单的组件,并没有使用@XxlJob标注。

然后,在调度中心新建一个任务,模式设置为GLUE:

image-20250802141109076

保存后在任务列表,选中新创建的任务,操作按钮下拉选型选择GLUE IDE,打开在线源码编辑器:

输入以下代码:

java
package org.example.springbootdemo.job;

import com.xxl.job.core.handler.IJobHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class GLUEJogExecutor extends IJobHandler {

    @Autowired
    private GLUEJob glueJob;

    @Override
    public void execute() throws Exception {
        glueJob.method1();
    }
}

image-20250802141225926

输入版本号后(方便回溯)保存关闭。

然后,启动任务:

image-20250802141405410

可以发现任务顺利运行。

总结,GLUE模式可以让非任务方法实时变为任务。

3.3 服务多实例(路由策略)

由于XXL- JOB是分布式任务调度框架,所以本小节启动两个微服务,注意,由于在同一台机上启动多个微服务,有可能造成端口冲突,所以需要配置为不同的端口(VM参数):

txt
-Dserver.port=8081 -Dxxl.job.executor.port=9998

image-20250802143332022

启动成功后,在执行器界面也能看到两个节点:

image-20250802144203747

回到第一个测试任务helloJob,启动任务,会发现任务只在一个服务实例上运行,这保证了任务不会被重复执行。

在创建任务时,有一个配置路由策略,可以配置当有多个执行器时,选择哪一个执行器执行任务,选项如下:

  • 第一个:总是选择可用执行器列表中的第一个执行器。

  • 最后一个:总是选择可用执行器列表中的最后一个执行器。

  • 轮询:按照顺序依次选择下一个可用执行器,实现轮流执行。

  • 随机:每次从可用执行器列表中随机选择一个执行器。

  • 一致性HASH:根据任务的调度参数(例如,JobId、任务参数等)计算哈希值,然后根据哈希值选择对应的执行器。相同的哈希值总是路由到同一个执行器。

  • 最不经常使用:选择历史调度次数最少的那个执行器来执行任务。

  • 最近最久未使用:选择历史调度时间最久远的那个执行器来执行任务(即,最近没有被调度的执行器)。

  • 故障转移:按照执行器列表顺序依次尝试调度,如果当前执行器失败,则尝试下一个,直到成功。

  • 忙碌转移:当调度中心发现某个执行器正在忙碌(例如,正在执行大量任务,或者某个任务长时间未返回)时,会尝试将任务调度到集群中其他不那么忙碌的执行器上。

  • 分片广播:将一个任务拆分成多个分片,然后将这些分片广播到所有可用的执行器上,每个执行器独立地处理分配给自己的分片。在执行器中,使用如下方式获取总分片和当前分片:

    java
    // 获取总分片
    int shardTotal = XxlJobHelper.getShardTotal();
    // 获取当前分片
    int shardIndex = XxlJobHelper.getShardIndex();

    例如,在调度中心修改路由策略为分片广播,然后在执行器任务中获取当前分片和总分片:

    java
    @XxlJob(value = "helloJob")
    public void execute() {
        // 获取总分片
        int shardTotal = XxlJobHelper.getShardTotal();
        // 获取当前分片
        int shardIndex = XxlJobHelper.getShardIndex();
    
        logger.info(String.format("分片参数:当前分片序号 = %s, 总分片数 = %s", shardIndex, shardTotal));
    }

    结果发现每个执行器节点都会被调用,这样可以并行执行任务。

4. CRON表达式

CRON 表达式 是一种用于配置定时任务的字符串,它指定了任务在什么时候运行。

一个标准的 Cron 表达式由 六个或七个字段 组成,每个字段代表一个时间单位,并用空格分隔。每个字段都有特定的取值范围和含义:

  1. :取值范围 0-59
  2. 分钟:取值范围 0-59
  3. 小时:取值范围 0-23 (24 小时制)。
  4. 日期 (月份中的天):取值范围 1-31
  5. 月份:取值范围 1-12 (或使用 JAN, FEB 等缩写)。
  6. 星期几:取值范围 0-7 (其中 07 都代表星期日,1 代表星期一,以此类推;或使用 SUN, MON 等缩写)。
  7. 年份 (可选):取值范围 1970-2099

大部分情况下,使用的是 六个字段 的 Cron 表达式,省略了年份。

image-20241223191222682

Cron 表达式通过使用一些特殊字符来提供更强大的调度能力:

  • ***** (星号):表示“每”或“所有”。例如,在小时字段中使用 * 表示每小时。
  • ? (问号):表示“不指定”。通常用于日期和星期几字段,因为这两者可能存在冲突。例如,如果指定了特定的日期,就不需要再指定星期几,反之亦然。
  • - (连字符):表示范围。例如,在小时字段中使用 9-17 表示从上午 9 点到下午 5 点的每个小时。
  • , (逗号):表示列表值。例如,在星期几字段中使用 MON,WED,FRI 表示每周一、周三和周五。
  • / (斜杠):表示步长或增量。例如,在分钟字段中使用 0/15 表示从第 0 分钟开始,每 15 分钟执行一次 (即 0, 15, 30, 45 分)。
  • L (last):表示“最后”。
    • 在日期字段中使用 L 表示该月的最后一天。
    • 在星期几字段中使用 L 表示该月的最后一个星期几(例如,5L 表示该月的最后一个星期五)。
  • W (weekday):表示“最近的工作日”。
    • 在日期字段中使用 nW 表示离指定日期 n 最近的工作日。例如,15W 表示离该月 15 号最近的工作日。
  • # (哈希):用于星期几字段,表示该月的第几个星期几。例如,6#3 表示该月的第三个星期六。

一些CRON表达式示例:

  • 0 0 * * * ?:每天午夜 0 点执行。
  • 0 15 10 * * ?:每天上午 10:15 执行。
  • 0 0/5 14,18 * * ?:每天下午 2 点到 2 点 55 分之间以及下午 6 点到 6 点 55 分之间,每隔 5 分钟执行一次。
  • 0 0 10 L * ?:每月最后一天上午 10 点执行。
  • 0 0 10 ? * 6#3:每月第三个星期六上午 10 点执行。