

作者:小傅哥 博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞
本文的宗旨在于通过简单干净实践的方式教会读者,多种类型的任务执行组件使用案例,包括;Quartz 使用、扩展 Spring-Schedule 自动增加任务、XXL-Job 分布式任务调度。其中像 Spring-Schedule 小傅哥还添加了一些 Spring 组件开发的能力可自动扩展任务、对 XXL-Job 的配置引入了 Docker Compose 自动化安装和自动初始化 MySQL 数据库 xxl-job.sql 库表数据。这些都是为了让你在不同的场景选择合适的框架,同时也能更简单的使用这些框架。
本章节的任务调度组件会放到 DDD 的 Trigger 模块中,也就是触发器层。我们认为所有的调用行为,HTTP、RPC、MQ、任务,都是一个触发的入口,所以对于任务调度也放到这一层使用。
本文涉及的工程:
xxl-job 已提供了最简化安装自动导入库表操作任务调度是一个非常重要的功能组件,常作用于;定时清理数据 - 冷数据迁移、活动状态扫描 - 过期活动关闭、消息发送补偿 - MQ失败重发、支付掉单补偿 - 支付幂等重试,等各类场景都会用到任务调度组件。它可以帮我们执行确定规则的业务或功能流程。

当你的微服务应用是一组较小的模型结构时,其实任务与服务结合在一起即可,让它与自己的领域绑定。但如果微服务的体量很大,那么这组微服务所对应的任务也会较多,同时需要一些分布式的能力,让调度的算力可以更快更好的运用起来。
所以一般这个时候就需要引入把任务单独拆分出一个微服务系统,一般可以叫做 xxx-worker 系统,他们就是专门处理任务的一个个执行器。把这些执行器注册到任务调度中心,由任务调度中心统一管理各项任务的执行。这样如果有一个任务在一个算力执行器上失败或者说执行器宕机了,那么可以把任务迁移到其他算力执行器上执行。这就是分布式的好处。

本案例所需安装的环境主要是 XXL-Job 的一套 MySQL 库和 XXL-Job 应用以及对应的库表初始化。为了让大家使用起来更加简单,小傅哥这里提供了一套 compose.yml 支持 AMD 和 ARM 架构使用。

文件:docs/xxl-job/xxl-job-docker-compose.yml
# 命令执行 docker-compose up -d
version: '3.9'
services:
# http://127.0.0.1:9090/xxl-job-admin admin/123456 - 安装后稍等会访问即可
# 官网镜像为 xuxueli/xxl-job-admin 但不支持ARM架构【需要自己打包】,所以找了一个 kuschzzp/xxl-job-aarch64:2.4.0 镜像支持 AMD/ARM
xxl-job-admin:
image: kuschzzp/xxl-job-aarch64:2.4.0
container_name: xxl-job-admin
restart: always
depends_on:
- mysql
ports:
- "9090:9090"
links:
- mysql
volumes:
- ./data/logs:/data/applogs
- ./data/xxl-job/:/xxl-job
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/xxl_job?serverTimezone=UTC&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=123456
- SERVER_PORT=9090
# MySQL 8.0.32 支持 AMD/ARM
mysql:
image: mysql:8.0.32
container_name: mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
TZ: Asia/Shanghai
# MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' 可配置无密码,注意配置 SPRING_DATASOURCE_PASSWORD=
MYSQL_ROOT_PASSWORD: 123456
MYSQL_USER: xfg
MYSQL_PASSWORD: 123456
depends_on:
- mysql-job-dbdata
ports:
- "13306:3306" # 如果你无端口占用,可以直接使用 3306
volumes:
- ./sql:/docker-entrypoint-initdb.d
volumes_from:
- mysql-job-dbdata
# 自动加载数据
mysql-job-dbdata:
image: alpine:3.18.2
container_name: mysql-job-dbdata
volumes:
- /var/lib/mysql

# /usr/local/bin/docker-compose -f /docs/xxl-job/xxl-job-docker-compose.yml up -d - 比较适合在云服务器上执行。这里小傅哥把 SQL 文件下载到了本地,用于初始化安装使用depends_on - 依赖于谁先安装、MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' - 可以设置MySQL无密码安装、mysql-job-dbdata - 一个启动安装数据库初始化脚本的镜像。并且需要在 MySQL 安装时使用 volumes_from 标签引入。地址:http://127.0.0.1:9090/xxl-job-admin - admin/123456 - 安装后稍等启动完成,就可以访问啦。

执行器的作用,就是让 xxl-job-admin 这个任务调度系统,调用注册上来的执行器完成任务的执行。客户端需要配置好这里的执行器名称才能注册上来。你可以根据自己的需要新增新的执行器,也可以在测试的时候使用默认的这个执行器名称。

任务的作用,就是执行器下具体的执行方法,按照配置的时间下发到任务中执行。
@Slf4j
@Component
public class XXLJob {
@XxlJob("demoJobHandler")
public void doJob() {
// 可以在任务中,调用一些业务方法逻辑的实现,如定时扫描超时未支付订单为关单处理,恢复库存
log.info("执行任务 - XXL-Job - 01");
}
}


引入POM
<!-- Quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>3.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>
添加配置
# xxl-job https://www.xuxueli.com/xxl-job/#%E6%AD%A5%E9%AA%A4%E4%B8%80%EF%BC%9A%E8%B0%83%E5%BA%A6%E4%B8%AD%E5%BF%83%E9%85%8D%E7%BD%AE%EF%BC%9A
xxl:
job:
# 验证信息 官网Bug https://github.com/xuxueli/xxl-job/issues/1951
accessToken: default_token
# 注册地址
admin:
addresses: http://localhost:9090/xxl-job-admin
# 注册执行器
executor:
# 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
address:
appname: xxl-job-executor-sample
# 执行器IP 配置为本机IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
ip:
# 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
port: 9999
# 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logpath: ./data/applogs/xxl-job/jobhandler
# 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
logretentiondays: 30
xxl-job 有一些必要的配置信息
源码:cn.bugstack.xfg.dev.tech.job.QuartzJob
@Slf4j
@Component()
public class QuartzJob {
@Scheduled(cron = "0/3 * * * * ?")
public void execute01() {
// 可以在任务中,调用一些业务方法逻辑的实现,如定时扫描超时未支付订单为关单处理,恢复库存
log.info("执行任务 - Quartz - 01");
}
@Scheduled(cron = "0/3 * * * * ?")
public void execute02() {
// 可以在任务中,调用一些业务方法逻辑的实现,如定时扫描超时未支付订单为关单处理,恢复库存
log.info("执行任务 - Quartz - 02");
}
}
配置任务注册器 - 在 app config 下
@Slf4j
@Configuration
@EnableScheduling
public class JobRegistrarAutoConfig implements SchedulingConfigurer {
private final ApplicationContext applicationContext;
public JobRegistrarAutoConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
Map<String, ExtScheduleJob> jobBeanMap = applicationContext.getBeansOfType(ExtScheduleJob.class);
Collection<ExtScheduleJob> jobBeans = jobBeanMap.values();
for (ExtScheduleJob job : jobBeans) {
ExtScheduleJobConfig extScheduleJobConfig = AnnotationUtils.findAnnotation(job.getClass(), ExtScheduleJobConfig.class);
if (extScheduleJobConfig == null || !extScheduleJobConfig.state()) continue;
log.info("启动任务 {} {}", extScheduleJobConfig.jobName(), extScheduleJobConfig.cronExpression());
taskRegistrar.addCronTask(job, extScheduleJobConfig.cronExpression());
}
}
}
源码:cn.bugstack.xfg.dev.tech.job.XXLJob
@Slf4j
@Component
public class XXLJob {
@XxlJob("demoJobHandler")
public void doJob() {
// 可以在任务中,调用一些业务方法逻辑的实现,如定时扫描超时未支付订单为关单处理,恢复库存
log.info("执行任务 - XXL-Job - 01");
}
}


23-08-05.14:19:42.003 [pool-2-thread-1 ] INFO QuartzJob - 执行任务 - Quartz - 01
23-08-05.14:19:42.003 [pool-2-thread-1 ] INFO ScheduleJob - 执行任务 - Schedule - 01
23-08-05.14:19:42.060 [xxl-job, JobThread-1-1691216327906] INFO XXLJob - 执行任务 - XXL-Job - 01
23-08-05.14:19:45.003 [pool-2-thread-1 ] INFO QuartzJob - 执行任务 - Quartz - 02
23-08-05.14:19:45.003 [pool-2-thread-1 ] INFO QuartzJob - 执行任务 - Quartz - 01
23-08-05.14:19:45.004 [pool-2-thread-1 ] INFO ScheduleJob - 执行任务 - Schedule - 01
23-08-05.14:19:45.041 [xxl-job, JobThread-1-1691216327906] INFO XXLJob - 执行任务 - XXL-Job - 01
0/3 * * * * ? 这样才能当下执行。另外如果你要测试的话,可以点执行一次。