XXL-JOB快速入门(什么是XXL-JOB、部署XXL-JOB、在SpringBoot项目中接入XXL-JOB、XXL-JOB中的核心概念、集群环境下任务的路由策略)_xxljob
文章目录
- 1. 分布式任务调度
- 2. @Scheduled注解的局限
-
- 2.1 集群环境下任务重复执行
- 2.2 CRON 表达式硬编码
- 2.3 任务失败无重试与统计
- 2.4 任务无法分片执行
- 2.5 其它局限
- 3. 什么是XXL-JOB
- 4. 通过源码部署调度中心
-
- 4.1 下载源码
- 4.2 源码说明
- 4.3 运行数据库脚本
- 4.4 补充:xxl_job数据库中八张表的作用
- 4.5 调度中心配置
-
- 4.5.1 数据库相关配置
- 4.5.2 日志相关配置
- 4.6 启动调度中心
- 4.7 访问调度中心
- 4.8 修改密码(可选)
- 4.9 补充:账号和密码的存放位置
- 5. 通过docker部署调度中心
-
- 5.1 拉取镜像
- 5.2 启动容器
- 5.3 开放防火墙的端口
- 5.4 访问调度中心
- 5.5 注意事项
- 6. 在SpringBoot项目中接入XXL-JOB
-
- 6.1 新建执行器
- 6.2 新建任务
- 6.3 引入依赖
- 6.4 编写与XXL-JOB相关的配置
- 6.5 编写XXL-JOB配置类
- 6.6 编写任务代码
- 6.7 启动任务
- 7. XXL-JOB中的核心概念
-
- 7.1 执行器(Executor)
- 7.2 任务(JOB)
-
- 7.2.1 执行器
- 7.2.2 任务描述
- 7.2.3 负责人
- 7.2.4 报警邮件
- 7.2.5 调度类型
- 7.2.6 运行模式
- 7.2.7 JobHandler
- 7.2.8 任务参数
- 7.2.9 阻塞处理策略
- 7.2.10 路由策略
- 8. 集群环境下任务的轮询路由策略
-
- 8.1 修改任务的执行策略为轮询
- 8.2 启动多个微服务
- 9. 集群环境下任务的分片广播路由策略
-
- 9.1 什么情况下采用分片广播
- 9.2 分片广播案例
-
- 9.2.1 创建执行器
- 9.2.2 创建任务
- 9.2.3 修改配置
- 9.2.4 修改任务代码
- 9.2.5 重启微服务
- 10. 执行器无法注册到配置中心
如果想了解更多与 XXL-JOB 相关的内容,可以查看 XXL-JOB 专栏: XXL-JOB
1. 分布式任务调度
分布式任务调度是一种在分布式系统中协调和执行任务的方法
在分布式计算环境中,任务调度器负责将任务分配到不同的计算节点上,以优化资源利用、提高任务处理效率、保证任务执行的可靠性
分布式任务调度的几个关键点:
- 任务分配:根据任务的性质、资源的需求以及各节点的状态,将任务合理地分配到不同的节点上执行
- 负载均衡:在多个节点之间平衡任务负载,避免某些节点过载而其他节点空闲的情况,从而提高系统整体的处理能力
- 容错机制:当某个节点发生故障时,任务调度系统能够将任务重新分配到其他健康的节点上,确保任务的连续性和系统的稳定性
- 资源管理:管理各个节点的资源使用情况,包括CPU、内存、存储和网络等,确保任务能够获得必要的资源
- 任务监控:监控任务的执行状态,收集性能数据,为调度决策提供依据
- 任务依赖管理:处理任务之间的依赖关系,确保前置任务完成后才开始后续任务
分布式任务调度的常见应用场景:
- 大数据处理:在处理大规模数据集时,通过分布式任务调度来实现数据的并行处理
- 实时计算:在需要实时响应的场景中,如实时数据分析、在线推荐系统等,分布式任务调度可以保证计算的时效性
- 批量作业:定时执行批量作业,如数据备份、报表生成等
- 微服务架构:在微服务架构中,分布式任务调度有助于服务之间的协调和通信
分布式任务调度的工具和框架有很多,如Apache Hadoop的YARN、Apache Mesos、Kubernetes、以及国产的调度工具如XXL-JOB、Elastic-Job等
分布式任务调度的工具和框架提供了任务调度、资源管理、容错处理等功能,使得在分布式环境中管理和执行任务变得更加高效和可靠
2. @Scheduled注解的局限
利用 Spring 框架提供的 @Scheduled
注解可以比较方便地设置定时任务,但是使用 @Scheduled
注解存在以下局限
2.1 集群环境下任务重复执行
在集群部署时,每个服务实例都会独立触发定时任务,导致同一任务在多个节点上重复执行
例如,一个日志清理任务可能会在3个节点上会同时运行3次,导致数据不一致或造成资源竞争
虽然可以通过分布式锁(如Redis锁、数据库锁、ZooKeeper锁)确保同一时间只有一个节点执行任务,但这需要额外的代码实现和运维成本
2.2 CRON 表达式硬编码
@Scheduled
的 cron
属性通常直接写在注解中(如 cron = \"0 0 * * * ?\"
),修改时需要重新编译代码,无法动态调整
即使外部化配置,仍需重启服务才能生效,无法像 Quartz 那样通过数据库动态修改触发规则
@Scheduled(cron = \"${task.cron}\")public void task() { ... }
2.3 任务失败无重试与统计
@Scheduled
注解本身不提供失败重试机制,任务执行失败后直接终止,且无法记录执行历史、成功率等统计信息
2.4 任务无法分片执行
当任务需要处理海量数据时(如全表扫描),单个任务实例可能成为性能瓶颈,而 @Scheduled
无法自动将任务拆分为多个分片并行执行
2.5 其它局限
-
单线程执行 :
@Scheduled
注解默认使用单线程执行所有定时任务,若某个任务耗时较长,会阻塞后续任务(可通过自定义TaskScheduler
配置线程池解决) -
无可视化管理界面 :Spring的定时任务功能没有提供图形化的管理界面,开发者无法直观地查看任务状态、执行历史或进行动态配置。所有任务的调度、监控和调整都需要通过代码或日志实现,增加了运维的复杂度
3. 什么是XXL-JOB
XXL-JOB 是一个分布式任务调度平台,由中国人许雪里(一个来自美团的程序员)开发
XXL-JOB 的核心设计目标是开发迅速、学习简单、轻量级、易扩展,XXL-JOB 现已开放源代码并接入多家公司的线上产品线
源码地址:许雪里/xxl-job
https://gitee.com/xuxueli0323/xxl-job
文档地址:XXL开源社区
https://www.xuxueli.com/xxl-job/
4. 通过源码部署调度中心
调度中心环境要求
- Maven3+
- Jdk1.8+
- Mysql5.7+
4.1 下载源码
我们先下载 XXL-JOB 的源码,下载地址:许雪里/xxl-job
https://gitee.com/xuxueli0323/xxl-job
下载 zip 压缩包
4.2 源码说明
由于 XXL-JOB 是用 Java 开发的,我们可以用 IntelliJ IDEA 打开 XXL-JOB 的源码
4.3 运行数据库脚本
找到 doc 目录下的 tables_xxl_job.sql
文件,执行 SQL 文件(XXL-JOB 的运行依赖于一个名为 xxl-job
数据库,数据库中有 8 张表)
4.4 补充:xxl_job数据库中八张表的作用
八张表的作用如下
4.5 调度中心配置
4.5.1 数据库相关配置
配置文件位置:xxl-job-master/xxl-job-admin/src/main/resources/application.properties
修改与数据库相关的信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
4.5.2 日志相关配置
配置文件位置:xxl-job-master/xxl-job-admin/src/main/resources/logback.xml
修改日志文件的存放路径,对日志不是很了解的同学,可以看一下我的另一篇博文:在SpringBoot项目中优雅地记录日志(日志框架选型、SpringBoot默认的日志实现框架、如何使用日志框架、如何切换日志的具体实现框架、与日志相关的配置、记录日志的最佳实践)
4.6 启动调度中心
启动 XxlJobAdminApplication
4.7 访问调度中心
在浏览器输入以下地址
http://localhost:8080/xxl-job-admin
- 默认的登录账号:admin
- 默认的登录密码:123456
成功登录后的界面
4.8 修改密码(可选)
在调度中心修改密码
4.9 补充:账号和密码的存放位置
XXL-JOB 的账号和密码存放在 xxl_job
数据库的 xxl_job_user
表中
表中默认有一个名为 admin 的用户,密码是 123456(数据库中存储的密码是经过加密后的字符串)
- role:角色:0-普通用户、1-管理员
- permission:权限:执行器ID列表,多个逗号分割
5. 通过docker部署调度中心
5.1 拉取镜像
sudo docker pull xuxueli/xxl-job-admin:2.4.1
下载好 xxl-job 的镜像后,可以将镜像保存为 tar 文件,下载到 Windows 本地,方便下一次在另一个 Linux 系统上运行
sudo docker save xuxueli/xxl-job-admin:2.4.1 -o /tmp/xxl-job-admin-2.4.1.tar
sudo chmod +rx /tmp/xxl-job-admin-2.4.1.tar
5.2 启动容器
修改与数据库有关的信息
sudo docker run \\ --env \'SPRING_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/xxl_job?serverTimezone=Asia/Shanghai&Unicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true\' \\ --env \'SPRING_DATASOURCE_USERNAME=wuyanzu\' \\ --env \'SPRING_DATASOURCE_PASSWORD=!UpuSZAxG#1&2^cG\' \\ --env \'XXL_JOB_ACCESSTOKEN=nEwghQF8a4nDv4ZY\' \\ --env \'SERVER_PORT=8080\' \\ -p 8080:8080 \\ -v /tmp:/data/applogs \\ --name xxl-job-admin \\ -d xuxueli/xxl-job-admin:2.4.1
--env \'SPRING_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/xxl_job?serverTimezone=Asia/Shanghai&Unicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true\'
:设置环境变量,指定 Spring Boot 应用的数据源 URL,这里连接到本地的 MySQL 数据库,数据库名为xxl_job
,设置服务器的时区为亚洲/上海,设置字符编码为 UTF-8,允许客户端从服务器检索公钥--env \'SPRING_DATASOURCE_USERNAME=wuyanzu\'
:设置环境变量,指定连接数据库的用户名为wuyanzu
--env \'SPRING_DATASOURCE_PASSWORD=!UpuSZAxG#1&2^cG\'
:设置环境变量,指定连接数据库的密码--env \'XXL_JOB_ACCESSTOKEN=nEwghQF8a4nDv4ZY\'
:设置环境变量,指定 XXL-Job 的访问令牌--env \'SERVER_PORT=8080\'
:设置环境变量,指定应用运行的端口号为 8080-p 8080:8080
:映射容器端口到宿主机端口,这里将容器的 8080 端口映射到宿主机的 8080 端口-v /tmp:/data/applogs
:挂载宿主机的目录到容器内,这里将宿主机的/tmp
目录挂载到容器的/data/applogs
目录,用于存储日志--name xxl-job-admin
:指定容器的名称为xxl-job-admin
-d xuxueli/xxl-job-admin:2.4.1
:指定以守护态(后台运行)运行容器,并指定使用的镜像为xuxueli/xxl-job-admin
的2.4.1
版本
5.3 开放防火墙的端口
为了能够从外界访问 XXL-JOB,需要为 XXL-JOB 开放防火墙的 8080 端口
- 如果你使用的是云服务器,在安全组中放行 8080 端口
- 如果你安装了宝塔,除了在安全组中放行 8080 端口,可能还要在宝塔中放行 8080 端口
5.4 访问调度中心
在浏览器输入以下地址(将 localhost 更改为你的 IP 地址)
http://localhost:8080/xxl-job-admin
- 默认的登录账号:admin
- 默认的登录密码:123456
5.5 注意事项
XXL-JOB 的运行依赖于数据库,通过 docker 部署 XXL-JOB 时,确保调度中心能连接到数据库,使用一个公网可以访问的数据库是最简单的方法
如果没有公网可以访问的数据库,可以用 docker 额外部署一个数据库容器,接着利用 docker 创建一个网络,将 XXL-JOB 容器和数据库容器加入到同一个网络中,也可以直接使用 docker-compose 编排 XXL-JOB 容器和数据库容器
如果对docker-compose不太了解,可以参考我的另一篇博文:DockerCompose详解(DockerCompose简介、docker-compose.yml文件的基本结构、常用的DockerCompose命令、docker-compose.yml文件示例)
6. 在SpringBoot项目中接入XXL-JOB
6.1 新建执行器
在调度中心中新建执行器(默认情况下会有一个名为 xxl-job-executor-sample
的执行器)
6.2 新建任务
在调度中心中新建任务(默认情况下会有一个名为 测试任务1
的任务)
任务的 JobHandler
属性下面会用到,0/1 * * * * ? *
表示每秒执行一次
0/1 * * * * ? *
6.3 引入依赖
<dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.3.0</version></dependency>
6.4 编写与XXL-JOB相关的配置
XXL-JOB 会启动一个内嵌的服务器,该内嵌服务器默认会占用 9999 端口,可以手动指定内嵌服务器占用的端口
application.yml
server: port: 8881xxl: job: accessToken: default_token admin: addresses: http://127.0.0.1:8080/xxl-job-admin executor: app-name: xxl-job-executor-sample ip : localhost port: 11015
6.5 编写XXL-JOB配置类
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.Configuration;@Configurationpublic class XxlJobConfiguration { private final Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class); @Value(\"${xxl.job.admin.addresses}\") private String adminAddresses; @Value(\"${xxl.job.executor.app-name}\") private String appName; @Value(\"${xxl.job.executor.ip}\") private String ip; @Value(\"${xxl.job.executor.port}\") private Integer port; @Value(\"${xxl.job.accessToken}\") private String accessToken; @Bean public XxlJobSpringExecutor xxlJobSpringExecutor() { logger.info(\"============================== xxlJobSpringExecutor ==============================\"); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); xxlJobSpringExecutor.setAppname(appName); xxlJobSpringExecutor.setAccessToken(accessToken); xxlJobSpringExecutor.setIp(ip); xxlJobSpringExecutor.setPort(port); xxlJobSpringExecutor.setLogPath(\"F:\\\\HeiMaTouTiao\\\\xxl-job\\\\executor\"); logger.info(\"============================== xxlJobSpringExecutor ==============================\"); return xxlJobSpringExecutor; }}
6.6 编写任务代码
使用 @XxlJob
注解指定要执行哪个任务
import com.xxl.job.core.handler.annotation.XxlJob;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Componentpublic class HelloXxlJob { @Value(\"${server.port}\") private String port; @XxlJob(\"demoJobHandler\") public void echo() { System.out.println(\"Hello, XXL-JOB\" + port); }}
6.7 启动任务
先启动 SpringBoot 应用程序(SpringBoot 应用程序启动后会自动注册到调度中心)
接着在调度中心启动任务
任务启动后就能在控制台看到输出了
7. XXL-JOB中的核心概念
7.1 执行器(Executor)
在 XXL-JOB 中,执行器(Executor)是指那些实际执行任务的远程服务。每个执行器都会向 XXL-JOB 的调度中心(Admin)注册自己,并定期发送心跳以保持其注册状态
XXL-JOB 中执行器的几个关键点:
- 注册和心跳:执行器启动后,会向调度中心注册,并在
xxl_job_registry
表中记录其地址(通常是 IP 地址和端口号)。执行器还会定期发送心跳,以证明自己仍然在线并能够接收任务 - 任务执行:当调度中心接收到任务执行请求时,它会根据任务的配置选择一个合适的执行器来执行任务
- 地址记录:在
xxl_job_registry
表中,执行器的地址记录为registry_value
。如果这个值是localhost
,那么它通常意味着执行器和调度中心部署在同一台服务器上 - 本地通信:如果执行器的
registry_value
是localhost
,调度中心通过本地回环接口与执行器通信,不经过网络接口,因此没有网络延迟。 - 端口监听:执行器需要监听一个端口(例如
9999
),以便调度中心可以发送任务执行请求到这个端口 - 配置注意事项:
- 确保执行器监听的端口没有被其他服务占用
- 如果服务器上有防火墙,需要允许本地回环接口上的通信
- 考虑到可维护性和可扩展性,建议在非测试和开发环境中使用具体的 IP 地址或域名,而不是
localhost
- 迁移和扩展:如果将来需要将执行器迁移到不同的服务器,或者需要扩展系统以处理更多任务,那么需要更新
xxl_job_registry
表中的registry_value
,以反映执行器的新地址
总的来说,XXL-JOB 中的执行器是任务执行的关键组件,它需要正确地注册和配置,以确保调度中心可以有效地分配和执行任务
7.2 任务(JOB)
7.2.1 执行器
每个任务必须绑定一个执行器,方便对任务进行分组
7.2.2 任务描述
任务的描述信息,便于任务管理
7.2.3 负责人
任务的负责人
7.2.4 报警邮件
任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔
7.2.5 调度类型
- 无:该类型不会主动触发调度
- CRON:该类型将会通过 CRON 表达式触发任务调度
- 固定速度:该类型将会以固定速度,触发任务调度(按照固定的间隔时间,周期性触发)
7.2.6 运行模式
- Bean 类型任务:
- 概述:Bean 类型任务通常指的是通过 Spring 容器管理的 Java Bean 类
- 执行方式:调度中心会将任务交给 Spring 容器管理的 Java Bean 类进行执行。这种方式适合需要依赖 Spring 环境的任务
- 依赖:任务需要依赖 Spring 容器,因此需要将任务对应的 Java Bean 类添加到 Spring 容器中
- 配置:在 XXL-JOB 的配置中,需要指定任务的 Bean 类名
- Glue 类型任务:
- 概述:Glue 类型任务通常指的是外部的脚本文件,如 Shell、Python、Java 等
- 执行方式:调度中心会将任务交给指定的脚本文件进行执行。这种方式适合需要执行外部脚本的任务
- 依赖:任务不需要依赖 Spring 容器,可以直接执行外部脚本
- 配置:在 XXL-JOB 的配置中,需要指定任务的 Glue 类型(如 Shell、Python、Java 等)和对应的脚本文件路径
7.2.7 JobHandler
运行模式为 BEAN
模式时生效,对应 Bean 中 @XxlJob
注解中声明的 value 值
7.2.8 任务参数
任务执行所需要的参数
7.2.9 阻塞处理策略
调度过于密集,执行器来不及处理时的处理策略:
- 单机串行:调度请求进入单机执行器后,调度请求进入 FIF0(First Input First 0utput)队列,以串行方式运行
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务
7.2.10 路由策略
在 XXL-JOB 中,执行器集群部署时,有以下路由策略:
- 第一个(FIRST):任务总是固定选择第一个可用的执行器
- 最后一个(LAST):任务总是固定选择最后一个可用的执行器
- 轮询(ROUND):按照顺序轮流选择可用的执行器
- 随机(RANDOM):随机选择在线的机器
- 一致性HASH(CONSISTENT HASH):使用一致性哈希算法,每个任务固定选择某一台机器,且所有任务均匀散列在不同机器上
- 最不经常使用(LEAST FREQUENTLY_USED):使用频率最低的机器优先被选举
- 最近最久未使用(LEAST RECENTLY_USED):最久未使用的机器优先被选
- 故障转移(FAILOVER):当第一个执行器失败时,依次尝试下一个执行器,直到找到可用的执行器
- 忙碌转移(BUSYOVER):当第一个执行器忙碌时,依次尝试下一个执行器,直到找到空闲的执行器
- 分片广播(SHARDING BROADCAST):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数
8. 集群环境下任务的轮询路由策略
我们演示一下集群环境下任务的轮询路由策略
8.1 修改任务的执行策略为轮询
8.2 启动多个微服务
右键微服务,点击复制配置
点击修改选项
点击添加虚拟机选项
在虚拟机选项中填入以下内容
-Dserver.port=8882 -Dexecutor.port=11016
接着修改 application.yml
文件,修改内容如下
port: ${executor.port:11015}
右键复制出来的微服务,启动程序
最后启动任务,就可以分别在两个微服务的控制台中看到运行结果了
9. 集群环境下任务的分片广播路由策略
9.1 什么情况下采用分片广播
集群环境下,如果任务的路由策略为分片广播,一次任务调度将会广播集群中的所有执行器
举个例子,支付宝的花呗每个月 10 号都会通知用户还款,由于支付宝花呗的用户很多,任务量会特别大,如果使用轮询的方式,任务的执行效率就会变得很低
也就是说,在同一个时间点,要执行大量任务的情况下,会用到分片广播
假如现在有 8 个任务,集群中有三台机器,如果我们要同时地执行任务,就需要为每一个实例分配任务
那 XXL-JOB 是如何做的呢
假设现在集群中还是有三个实例,XXL-JOB 会通过任务 id 取模(对集群中的实例数取模)的方式让具体的分片执行任务
9.2 分片广播案例
需求:让两个节点同时执行 10000 个任务,每个节点分别执行 5000 个任务
9.2.1 创建执行器
我们新建一个分片执行器
xxl-job-sharding-executor
9.2.2 创建任务
我们新建一个任务,用于演示分片广播(记得勾选更改执行器)
0/5 * * * * ? *
shardingJobHandler
9.2.3 修改配置
修改 application.yml 文件中执行器的名字
xxl-job-sharding-executor
9.2.4 修改任务代码
在 HelloXxlJob 类中添加以下代码
- index:当前分片的序号(从0开始),执行器集群列表中当前执行器的序号
- total:总分片数,执行器集群的总机器数量
@XxlJob(\"shardingJobHandler\")public void shardingJobHandler() { // 分片参数 int shardIndex = XxlJobHelper.getShardIndex(); int shardTotal = XxlJobHelper.getShardTotal(); // 业务逻辑 List<Integer> list = getList(); for (Integer integer : list) { if (integer % shardTotal == shardIndex) { System.out.println(\"当前第\" + shardIndex + \"分片执行了,任务项为:\" + integer); } }}public List<Integer> getList() { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { list.add(i); } return list;}
9.2.5 重启微服务
分片广播方式:实例 A 和 B 每秒同时接收 10000 个任务,但根据各自分片 id,实例 A 只会处理其中的 5000 个,跳过另外 5000 个,实例 B 同理
重启两个微服务,就可以在控制台中看到分片广播的效果了
10. 执行器无法注册到配置中心
参考我的另一篇博文:XXL-JOB执行任务的SpringBoot程序无法注册到调度中心