在实际项目中,为了降低耦合,通常会把定时任务的逻辑单独抽离出来,构建成一个新的工程。也有可能需要定时任务实现高可用,组建成集群,提高容错率。
那么问题也就来了。既然定时任务是多个节点,那么同一时间多个节点都执行,必然造成数据重复,如何保证只有一个节点执行任务就是一个很重要的问题。
使用过原生定时任务的开发者应该深有感触,原生定时任务仅能满足简单的需求,应对复杂的场景还有一定的缺陷。如以下的一些不足之处:
上面介绍了原生定时任务的缺陷,而XXL-JOB
几乎能完美解决这些问题。XXL-JOB是一款开源免费的分布式任务调度框架,学习成本低,易扩展,依赖组件少,仅需要基础Java环境和Mysql数据库就可以使用,开箱即用。
XXL-JOB设计简单实用:
注意:要增加其他的报警时,需要新增类并且实现com.xxl.job.admin.core.alarm.JobAlarm接口,并把对象交给spring管理。具体规则可参考com.xxl.job.admin.core.alarm.impl.EmaiJobAlarm类
XXL-JOB将分布式任务系统分为两个模块:调度中心
和执行器
。
调度中心本身不承担业务逻辑,而是主要向执行器发送调度请求。执行器则负责接收调度中心的请求并且执行真正的业务逻辑。这样将任务调度和执行过程高度解耦,更容易实现集群。
XXL-JOB在github或gitee上都可以找到相关的源码:
笔者这里使用2.2.0
版本,下载zip或tar.gz压缩包,解压完成后即可得到源码。
准备数据库表
作者已经准备了数据表,我们只需要导入到Mysql中即可。
导入后数据库如下图
8张表:
使用IDEA导入源码
导入源码后发现主要有三个模块:
执行器示例中有很多版本,如SpringBoot、Spring、无框架、JFinal、JBoot等提供用户参考构建自己的执行器。
到xxl-job-admin
项目的application.properties
文件,修改数据库信息及Email配置:
### xxl-job, datasource
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=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
### xxl-job, email
spring.mail.host=smtp.qq.com
spring.mail.port=25
[email protected]
# 授权码,自行获取
spring.mail.password=xxxxxxxxxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
### xxl-job, access token
xxl.job.accessToken=
spring.mail.password
的配置项的SMTP密码需要单独申请,非邮箱登录的密码。若不需要邮件报警,那么关于Email的配置不用修改。
xxl.job.accessToken
若服务端设置了这个,那么它的客户端也要去设置这个token.
配置完成后,可以运行主类XxlJobAdminApplication
启动服务。启动完成后,使用浏览器访问http://localhost:8080/xxl-job-admin,输入账号admin密码123456,登录进入运行报表界面。
Docker方式搭建调度中心
除了使用源码运行外,还可以用Docker方式一键搞定。
docker run -e PARAMS="--spring.datasource.username=root --spring.datasource.password=123456 --spring.datasource.url=jdbc:mysql://ip:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimeZone=Asia/Shanghai" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin -d xuxueli/xxl-job-admin:2.2.0
<dependency>
<groupId>com.xuxueligroupId>
<artifactId>xxl-job-coreartifactId>
<version>2.2.0version>
dependency>
server:
port: 8002
spring:
application:
name: user # 应用名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务地址
xxl:
job:
admin:
adresses: http://localhost:8080/xxl-job-admin
accessToken: #若服务端设置了,那么这里需要和服务端的值保持一致
executor:
appname: xxl-job-executor-sample
address: # 执行器地址,默认使用 xxl.job.executor.address配置项,若为空,则使用xxl.job.executor.ip + xxl.job.executor.port配置
ip: # 执行器ip
port: 9989 # 执行器端口
logpath: D:\logs # 日志保存路径
logretentiondays: 30 # 日志保留天数
在服务器是多网卡的情况下,自动获取的地址可能不对,这时候xxl.job.executor.address
或xxl.job.executor.ip
就派上用场了,手动设定地址。
@Configuration
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses; // 调度中心地址
@Value("${xxl.job.admin.accessToken}")
private String accessToken; // 通信token
@Value("${xxl.job.admin.executor.appname}")
private String appName; // 执行器名称
@Value("${xxl.job.admin.executor.address}")
private String address; // 地址
@Value("${xxl.job.admin.executor.ip}")
private String ip; // ip
@Value("${xxl.job.admin.executor.port}")
private int port; // 端口
@Value("${xxl.job.admin.executor.logpath}")
private String logPath; // 日志地址
@Value("${xxl.job.admin.executor.logretentiondays}")
private int logRetentionDays; // 日志保留天数
@Bean
public XxlJobSpringExecutor xxlJobSpringExecutor() {
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;
}
}
配置完成后,就可以启动执行器。然后来到调度中心的后台管理页面,点击执行器管理,如图所示
注意:如果注册方式是自动注册,会有心跳机制,OnLine机器地址列表服务自动上上线、下线。若是手动录入,则不会有心跳机制,而会一直存在OnLine机器的地址列表中。
目前已经有一个执行器了,下面使用GLUE模式创建一个定时任务。
注意:GLUE模式的执行代码托管到调度中心在线维护,相比Bean模式更加轻量化,但是发杂业务不建议使用GLUE模式。
路由策略:表示使用什么样的策略选出当前下拉框选择的执行器具体由哪个执行器执行任务(若是分片广播,那就是当前下拉框选择的执行器下全部执行器都执行)。
点击执行一次。XXL-JOB提供了手动触发执行一次任务的功能,必须等到设定的时间到达。
点击后,出现任务参数和机器地址的输入框(可不填),点击保存
即可。
若想定时任务按照设定的时间执行,需要将那条任务设置为启动
调度日志
中可以看到执行的日志信息。
前面使用的是GLUE(java)模式实现的定时任务,但是面对负责的业务逻辑时肯定不行的。所以使用BEAN模式实现普通定时任务。
@Component
public class TestJobHandler {
@XxlJob("sampleJobHandler")
public ReturnT<String> sampleJobHandler(String name) {
XxlJobLogger.log("sampleJobHandler, hello World.");
return ReturnT.SUCCESS;
}
}
启动执行器
XXL-JOB2.2.0版本移除了@JobHandler注解,推荐使用基于方法的@XxlJob注解进行任务开发。
分片任务适用于数据量较大的场景,采用分而治之的思想,尽量把任务均摊到每个节点,减少单个节点的压力。
例如有这样一个业务:每天固定一个时间生成代理用户的报表,考虑到代理比较多,计算数据的过程比较复杂时,使用分片任务。
@XxlJob("shardingJobHandler")
public ReturnT<String> shardingJobHandler(String param) {
// 分片任务
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
// 分片参数
XxlJobLogger.log("分片参数:当前序号 = {},总分片数:= {}",
shardingVO.getIndex(), shardingVO.getTotal());
for (Integer val : agentList) {
if (val % shardingVO.getTotal() == shardingVO.getIndex()) { // 取余处理
XxlJobLogger.log("第{}片,命中分片开始处理 {} ", shardingVO.getIndex(), val);
// 其他的业务逻辑
}
}
return ReturnT.SUCCESS;
}
ShardingVO中有两个属性:
index:当前分片索引(0开始)
total:总分片数
若使用分片任务,那么自然是需要2个节点以上的执行器才可以。复制一份执行器,改下端口即可server.port
和xxl.job.executor.port
,然后启动它们。
在调度中心后台增加分片任务
第二个分片
看出总6条代理数据,2个执行器执行的任务是均匀的,分片执行任务成功。
若有多个调度中心,那么需要在执行器的配置中:
addresses
多个地址时,使用,
分隔
xxl:
job:
admin:
addresses: http://localhost:8080/xxl-job-admin,http://localhost:8081/xxl-job-admin
accessToken:
executor:
appname: xxl-job-executor-sample
address: # 执行器地址,默认使用 xxl.job.executor.address配置项,若为空,则使用xxl.job.executor.ip + xxl.job.executor.port配置
ip: # 执行器ip
port: 9999 # 执行器端口
logpath: D:\logs # 日志保存路径
logretentiondays: 30 # 日志保留天数
同样也可以使用Nginx负载均衡,为多个调度中心设置调度分配
upstream XXLJOB {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 8002;
server_name localhost;
location / {
proxy_pass http://XXLJOB;
}
}
执行器就可以这样配置
xxl:
job:
admin:
addresses: http://localhost:8002/xxl-job-admin