一、后端xxl job的配置属性介绍
- enabled : 是否开启执行器,如果为false,调度中心就调用不了后端定时任务
- admin-addresses:调度中心的地址,多个则可以逗号拼接: url1,url2,url3
- access-token: 执行器通讯TOKEN ,必须和xxl-job代码里面的一致
src/main/resources/application.yml 配置文件中 accessToken: --- # xxljob系统配置 xxl: job: # 鉴权token accessToken: xxl-job # 国际化 i18n: zh_CN # 日志清理 logretentiondays: 30 triggerpool: fast: max: 200 slow: max: 100
appname: 执行器AppName, 配置完调度中心可以识别到
port: 执行器端口号,执行器名称可以一致appname: xxl-job-executor,但是如果多个执行器端口号必须区分开来- address: 执行器注册地址,默认自动获取IP
- ip: 执行器IP:默认自动获取IP
- logpath: 日志保存路径
logretentiondays: 日志保存天数
通过配置可以知道,xxl-job 调度中心识别执行器的流程:
开启执行器后,执行器根据admin-addresses 地址和 xxl-job 注册到 调度中心,并绑定执行器名称端口日志等信息
--- # xxl-job 配置
xxl.job:
# 执行器开关
enabled: true
#enabled: false
# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
admin-addresses: http://localhost:9100/xxl-job-admin
# 执行器通讯TOKEN:非空时启用
access-token: xxl-job
executor:
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
appname: xxl-job-executor
# 28080 端口 随着主应用端口飘逸 避免集群冲突
port: 2${server.port}
# 执行器注册:默认IP:PORT
address:
# 执行器IP:默认自动获取IP
ip:
# 执行器运行日志文件存储磁盘路径
logpath: ./logs/xxl-job
# 执行器日志文件保存天数:大于3生效
logretentiondays: 30
测试:启动调度中心和后台,将后台注册到调度中心
启动xxx-job调度中心和 后台执行器,
则可以看到注册成功:
二、任务管理
2.1 常用配置:
- 调度配置-调度类型:一般我们默认选择CRON表达式,可以参考文章:cron 定时任务_cron 每月最后一天-CSDN博客
- 任务配置-运行模式:默认BEAN模式,后面代码会演示 分片广播、命令行任务
- 任务配置-JobHandler:对应注解里面标注的名称 @XxlJob("demoJobHandler")
/** * 1、简单任务示例(Bean模式) */ @XxlJob("demoJobHandler") public void demoJobHandler() throws Exception { XxlJobHelper.log("XXL-JOB, Hello World."); for (int i = 0; i < 5; i++) { XxlJobHelper.log("beat at:" + i); } // default success }
高级配置-路由策略:
第一个(First):总是选择注册列表中的第一个执行器执行。 最后一个(Last):总是选择注册列表中的最后一个执行器执行。 轮询(Round):按顺序轮流选择执行器执行,实现简单负载均衡。 随机(Random):随机选择一个执行器执行,增加执行的不确定性。 一致性HASH(Consistent Hash):通过Hash算法稳定地选择执行器,即使执行器数量变化也能保持任务分配的连续性。 最少使用(Least Used)(LFU或类似概念):选择最近最少被使用的执行器,以平衡负载。 最近未使用(Least Recently Used, LRU):选择最久未被使用的执行器执行。 故障转移(Failover):当首选执行器不可用时,自动选择下一个可用的执行器。 忙碌转移(Busyover):不仅考虑故障,还考虑执行器的忙碌状态,避免过载。 分片广播(Sharding Broadcast):所有执行器都执行一次任务,但每个执行器可以处理不同的数据分片,适用于大数据量处理。
高级配置-阻塞处理策略:
单机串行(默认): 描述:任务按调度顺序一个接一个地执行,前一个执行完再执行下一个。 特点:简单,但可能导致任务等待时间长。 丢弃后续调度: 描述:如果执行器正忙,新任务会被丢弃。 特点:防止任务堆积,但可能会丢失任务。 覆盖之前调度: 描述:如果执行器正忙,会中断当前任务,立即执行新任务。 特点:确保新任务快速执行,但可能影响旧任务。
- 高级配置-任务超时时间 : 默认0, 表示不超时
- 高级配置-失败重试次数 : 默认0, 表示不重试
三、 ruoyi-job 模块
ruoyi-job 是我们定义统一编写定时任务的子模块,现在看看怎么编写第一个定时任务:
引入依赖xxl-job核心包、已经公共common包
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
</dependencies>
1- 编写 XxlJobProperties
@ConfigurationProperties(prefix = "xxl.job") 映射 yml中的配置属性
@Data
@ConfigurationProperties(prefix = "xxl.job")
public class XxlJobProperties {
private Boolean enabled;
private String adminAddresses;
private String accessToken;
private Executor executor;
@Data
@NoArgsConstructor
public static class Executor {
private String appname;
private String address;
private String ip;
private int port;
private String logPath;
private int logRetentionDays;
}
}
2- 编写 XxlJobConfig 配置类
主要就是启动执行器,并配置调度中心地址、token、执行器等等
注解解释:
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true") :
开启配置属性:xxl.job 中 enabled为true时候才会 注册, @Bean注解标注的xxlJobExecutor 类
@Slf4j
@Configuration //标注配置类
@EnableConfigurationProperties(XxlJobProperties.class) //启用配置属性
@AllArgsConstructor
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true") //开启配置属性:enabled为true时候才会 注册:xxlJobExecutor
public class XxlJobConfig {
private final XxlJobProperties xxlJobProperties;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
// 初始化 xxl-job 执行器
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
// 配置 调度中心地址
xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses());
// 配置 token
xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
// 配置 执行器
XxlJobProperties.Executor executor = xxlJobProperties.getExecutor();
//执行器相关属性
xxlJobSpringExecutor.setAppname(executor.getAppname());
xxlJobSpringExecutor.setAddress(executor.getAddress());
xxlJobSpringExecutor.setIp(executor.getIp());
xxlJobSpringExecutor.setPort(executor.getPort());
xxlJobSpringExecutor.setLogPath(executor.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays());
return xxlJobSpringExecutor;
}
}
3-编写定时任务
完成上面配置类,便可以直接编写定时任务
@Slf4j
@Service
public class SampleService {
/**
* 1、简单任务示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobHelper.log("beat at:" + i);
}
// default success
}
四、参数传递
更新任务时候设置参数,如图:
运行任务后台便可以接受到参数:
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
String jobParam = XxlJobHelper.getJobParam();
log.info("jobParam:{}" , jobParam);
for (int i = 0; i < 5; i++) {
XxlJobHelper.log("beat at:" + i);
}
// default success
}
结果:
2024-08-04 15:39:32 JRebel: Reconfiguring reprocessed bean 'sampleService' [com.ruoyi.job.service.SampleService]
2024-08-04 15:39:36 [xxl-job, JobThread-1-1722757089189] INFO com.ruoyi.job.service.SampleService
- jobParam:type=1&flag=true
当然,JSON也可以传递都后台,按需求进行处理即可
2024-08-04 15:43:09 [xxl-job, EmbedServer bizThreadPool-1028558242] INFO c.x.job.core.executor.XxlJobExecutor - >>>>>>>>>>> xxl-job regist JobThread success, jobId:1, handler:com.xxl.job.core.handler.impl.MethodJobHandler@6146a40f[class com.ruoyi.job.service.SampleService#demoJobHandler] 2024-08-04 15:43:09 [xxl-job, JobThread-1-1722757389734] INFO com.ruoyi.job.service.SampleService - jobParam:{“type”:1,&"code":0}
五、轮询策略
前置场景,启动2个端口不一样的执行器8080和8081端口,则会在2个注册的节点上轮流访问
总结:
- 执行器交替执行,减少服务器压力,实现负载均衡
- 如果一个节点挂了则会交给另一个节点处理
六、分片广播
场景:如果我们要同时通知1万个对象,当前有5个执行器,采用轮询则要循环1万次,所以此场景就应该采用分片广播策略
新增分片广播任务
后台处理:
对分片总是进行取余模拟,分片执行
/**
* 2、分片广播任务
*/
@XxlJob("shardingJobHandler")
public void shardingJobHandler() throws Exception {
// 分片索引
int shardIndex = XxlJobHelper.getShardIndex();
// 分片总数
int shardTotal = XxlJobHelper.getShardTotal();
//循环10次,模拟分片广播
for (int i = 0; i < 10; i++) {
//取余模拟,分片执行
if (i % shardTotal == shardIndex) {
XxlJobHelper.log("第 {} 片, 命中分片开始处理", i);
}
}
}
执行结果:
执行新增分片广播任务,生成2条调用日志
分别查看日志内容:
分片0执行了,第 0、4、6、8任务
分片1执行了,第 1、3、5、7、9任务
这样对比轮询去处理该场景任务,极大的提升了任务处理的速度
分片广播的应用场景
- 数据处理任务:如对大量数据进行清洗、分析、转换等操作,可以将任务拆分成多个小任务,分布式地执行。
- 分布式计算任务:如对大规模数据进行机器学习、深度学习等计算,可以将计算任务拆分成多个小任务,分布式地执行,加速计算过程。
- 并发请求任务:如对多个服务进行并发请求,可以将请求拆分成多个小请求,分布式地执行,提高请求的并发处理能力。
注意事项:
- 在实现分片广播任务时,需要确保执行器集群中的执行器回调地址(xxl.job.admin.addresses)保持一致。
- 同一个执行器集群内的AppName(xxl.job.executor.appname)也需要保持一致。
- 在任务执行过程中,需要考虑并发执行的情况,确保任务逻辑的正确性。
总之,XXL-JOB的分片广播功能为分布式任务调度提供了一种高效、灵活的实现方式,适用于多种需要并行处理的任务场景。
七、命令行任务
演示window下执行命令行,linux也是一样的
1-先新记事本建文件 mkdir.bat文件,内容如下:
命令行执行命令:新建一个文件夹,yyyy-MM-dd HH-mm-ss 作为文件夹名称
@echo off
setlocal enabledelayedexpansion
:: 获取当前日期
for /f "tokens=2-4 delims=/-" %%a in ('date /t') do (
set YY=%%c
set MM=%%a
set DD=%%b
)
:: 获取当前时间
for /f "tokens=1-3 delims=: " %%h in ('time /t') do (
set HH=%%h
set MM=%%i
set SS=%%j
)
:: 格式化日期和时间字符串以适应文件夹名
set "folderName=%YY%-%MM%-%DD%_%HH%-%MM%-%SS%"
:: 创建文件夹
md "D:\CPanFiles\download\!folderName!"
2-创建调度任务:
任务运行参数为上面mkdir.bat文件的地址,我放在D盘downlad文件夹下面
3-commandJobHandler 运行命令行后台如下:
获取定时任务参数,执行命令行
@XxlJob("commandJobHandler")
public void commandJobHandler() throws Exception {
//1-获取参数
String command = XxlJobHelper.getJobParam();
int exitValue = -1;
BufferedReader bufferedReader = null;
try {
// command
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(command);
processBuilder.redirectErrorStream(true);
// 执行命令行
Process process = processBuilder.start();
//Process process = Runtime.getRuntime().exec(command);
//字符流 读取
BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
// 记录到日志表
String line;
while ((line = bufferedReader.readLine()) != null) {
XxlJobHelper.log(line);
}
process.waitFor();
exitValue = process.exitValue();
} catch (Exception e) {
XxlJobHelper.log(e);
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
if (exitValue == 0) {
// default success
} else {
XxlJobHelper.handleFail("command exit value(" + exitValue + ") is failed");
}
}
前台点击执行任务:生成文件夹成功
八、HTTP平台请求
这里模拟发送请求到百度
httpJobHandler 后台:
拿到参数,发送HTTP请求,返回请求结果
@XxlJob("httpJobHandler")
public void httpJobHandler() throws Exception {
// param parse
String param = XxlJobHelper.getJobParam();
if (param == null || param.trim().length() == 0) {
XxlJobHelper.log("param[" + param + "] invalid.");
XxlJobHelper.handleFail();
return;
}
//获取http请求参数
String[] httpParams = param.split("\n");
String url = null;
String method = null;
String data = null;
for (String httpParam : httpParams) {
if (httpParam.startsWith("url:")) {
url = httpParam.substring(httpParam.indexOf("url:") + 4).trim();
}
if (httpParam.startsWith("method:")) {
method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase();
}
if (httpParam.startsWith("data:")) {
data = httpParam.substring(httpParam.indexOf("data:") + 5).trim();
}
}
//校验参数
if (url == null || url.trim().length() == 0) {
XxlJobHelper.log("url[" + url + "] invalid.");
XxlJobHelper.handleFail();
return;
}
if (method == null || !Arrays.asList("GET", "POST").contains(method)) {
XxlJobHelper.log("method[" + method + "] invalid.");
XxlJobHelper.handleFail();
return;
}
boolean isPostMethod = method.equals("POST");
// request 发送http请求
HttpURLConnection connection = null;
BufferedReader bufferedReader = null;
try {
// connection
URL realUrl = new URL(url);
connection = (HttpURLConnection) realUrl.openConnection();
// connection setting
connection.setRequestMethod(method);
connection.setDoOutput(isPostMethod);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setReadTimeout(5 * 1000);
connection.setConnectTimeout(3 * 1000);
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
// do connection
connection.connect();
// data
if (isPostMethod && data != null && data.trim().length() > 0) {
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.write(data.getBytes("UTF-8"));
dataOutputStream.flush();
dataOutputStream.close();
}
// valid StatusCode :获取http状态
int statusCode = connection.getResponseCode();
if (statusCode != 200) {
throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
}
// result :获取返回结果
bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
String responseMsg = result.toString();
XxlJobHelper.log(responseMsg);
return;
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
return;
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (Exception e2) {
XxlJobHelper.log(e2);
}
}
}
执行结果:跳转到百度登录页面:
9-生命周期
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
init :当 XXL-JOB 框架启动并加载此任务时,会尝试调用标注了
@XxlJob
注解的类中的init
方法(如果存在), 这可以用于执行一些初始化操作,比如资源加载、数据库连接初始化等destroy 当 XXL-JOB 框架停止或卸载此任务时,会尝试调用标注了
@XxlJob
注解的类中的destroy
方法(如果存在),这可以用于执行一些清理操作,比如关闭数据库连接、释放资源等。
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public void demoJobHandler2() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
}
public void init() {
log.info("init");
}
public void destroy() {
log.info("destory");
}
十、报警邮件配置
1-以qq邮箱举例接受错误报警提醒进入到安全设置(页面右上角)
2-生成授权码:
3-后台配置下 授权码(上面生成的 ) 以及 邮箱账号
--- # 邮件配置
spring:
mail:
from: 13361831qq.com
host: smtp.qq.com
username: 13361831qq.com
# 邮箱授权码
password: ixxxj0000jfoghbih
port: 25
properties:
mail:
smtp:
auth: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
starttls:
enable: true
required: true
4- 创建定时任务绑定邮箱
执行任务结果:收到信息成功