前言
由于之前我负责的那一块业务只有一个定时任务,所以我直接用的spring带的@Scheduled负责定时任务。现在有了新的定时任务的需求,就准备采用xxl-job框架进行定时任务的管理。需要注意的是请注意xxj-job版本迭代导致的定时任务开发部署导致的差异。本文使用的为xxj-job-2.3.1版本
1、拉取xxl-job代码
xxl-job是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。git地址:https://github.com/xuxueli/xxl-job/
直接在本地使用git拉取下来,拉取下来之后,发现有三个项目文件,分别是:xxx-job-admin,xxl-job-core,xxl-job-executor-samples。
2、使用maven进行编译
如果是想整合进项目,可以直接单独在项目外新建一个文件夹,使用idea打开拉取下来的代码,直接进行maven编译。因为有的项目的maven管理非常混乱(没错就是我自己的这个项目…)。
3、初始化数据库
拉取下来的源码只有数据库建表文件,位置在/doc/db/tables_xxl_job.sql。git上面的源码是mysql的建表语句,如果你的项目用的不是mysql,你需要为xxl-job新建一个数据库或者说修改xxl-job的代码,适配其他数据库,比如说oracle。我选择的是后者,这里需要修改建表语句和xml等操作,具体详见这个blog:xxl-job更换oracle数据源
4、部署调度中心
首先需要配置好调度中心的配置文件。调度中心配置文件地址:/xxl-job/xxl-job-admin/src/main/resources/xxl-job-admin.properties
下面是我的配置文件内容,仅供参考
### web 这里是你调度中心的端口和路径,这个路径就是调度中心的访问地址
server.port=5506
server.servlet.context-path=/xxl-job-admin
### actuator
management.server.servlet.context-path=/actuator
management.health.mail.enabled=false
### resources
spring.mvc.servlet.load-on-startup=0
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/
### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.##########
### mybatis
mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
mybatis-plus.configuration.jdbc-type-for-null=''
mybatis-plus.configuration.database-id=oracle
# 调度中心JDBC链接,配置上你自己项目的数据库以及密码即可
spring.datasource.url=jdbc:oracle:thin:@121.41.*.*:1521/orcl
spring.datasource.username=jy****
spring.datasource.password=jy****
### datasource-pool,这里使用的是oracle的数据库驱动
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.max-active=30
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.validation-query=select 1 from dual
spring.datasource.tomcat.validation-interval=30000
### 这里是警告邮箱配置
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=xxx@qq.com
spring.mail.from=xxx@qq.com
spring.mail.password=xxx
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
配置好以后,利用maven的打包工具即可将xxl-job-admin打包成相应的jar包,把这个jar包放到项目里的服务器上,使用java -jar xxl-job-admin.jar
即可启动xxl-job调度中心。当然这个命令只是在当前窗口运行,如果你Ctrl C退出或者直接关闭Shell,这个程序就会中止。如果想一直后台运行,可以使用另外一个命令:nohup java -jar xxl-job-admin.jar &
也可以自己写一个restart-xxl-job-admin.sh的xxl-job-admin应用重启文件。文件内容:
ps -ef|grep xxl-job-admin|grep -v grep|awk '{print $2}'|xargs kill -9
nohup java -Xms512M -Xmx512M -Dserver.port=5506 -jar xxl-job-admin-2.3.1-SNAPSHOT.jar > 5.log 2>6.log &
5、执行器组件部署
这一步需要将执行器部署到自己的项目内,这样才能协同xxl-job调度中心对项目内的定时任务进行管理。部署好执行器组件后即相当于xxl-job-executor-samples。
首先得在自己的项目内使用maven引入xxl-job-core的包,如果你的maven依赖拉取不了xxl-job-core的这个包,你也可以直接使用我们一开始使用git拉取下来的代码,将xxl-job-core部署打包,然后直接作为外部依赖插入到我们自己的项目中去。
其次,我们得在我们自己的需要使用定时任务管理的服务内的配置文件里,添加好xxl-job执行器的配置。
# 定时任务xxl-job配置
# log config
logging.config=classpath:logback.xml
### xxl-job调度中心地址,如果有多个,则用逗号分割
xxl.job.admin.addresses=http://120.26.*.*/xxl-job-admin
### xxl-job的tonke,可不填
xxl.job.accessToken=
### xxl-job执行器的名称
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job执行器的地址,可指定,不填会自动分发
xxl.job.executor.address=
### xxl-job执行器的ip和端口,可指定,不填会自动分发
xxl.job.executor.ip=
xxl.job.executor.port=5507
### xxl-job执行器的日志位置
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job执行器的过期清理时间
xxl.job.executor.logretentiondays=30
接着,在我们的项目的服务内,添加XxlJobConfig配置类。
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@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.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}
这样我们执行器也配置好了。
6、使用注解设置定时任务
在项目内的服务相关定时任务上面添加注解@XxlJob(value=“自定义jobhandler名称”)即可,方式格式要求为public void execute()
。需要注意的是请注意xxj-job版本迭代导致的定时任务开发部署导致的差异。比如:本文使用的为xxj-job-2.3.1版本,日志打印使用的XxlJobHelper,而不是XxlJobLogger。
/**
* 开发步骤:
* 1、任务开发:在Spring Bean实例中,开发Job方法;
* 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志;
* 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;
*
*/
@XxlJob("myJobHandler")
public void execute() {
// 获取参数
String param = XxlJobHelper.getJobParam();
try {
//书写业务逻辑
XxlJobHelper.log("myXxlJobHandler参数为..."+param);
//写日志到调度中心日志中
XxlJobHelper.log("myXxlJobHandler execute Success...");
// 设置任务结果
XxlJobHelper.handleSuccess();
} catch (Exception e) {
//写日志到调度中心日志中
XxlJobHelper.log("myXxlJobHandler execute Fail...");
// 设置任务结果
XxlJobHelper.handleFail();
}
}
}
现在只需要启动我们项目的这个微服务,执行器就会运行。就可以配合调度中心去管理我们设置好的定时任务。
7、配置调度中心
打开我们一开始配置的调度中心的地址,即我们部署调度中心的ip+端口+路径,由于我部署的那台服务器是使用nginx做路由转发的,我的路径为:http://120.26../xxl-job-admin。打开调度中心地址,默认登录账号 admin/123456,登录后运行界面如下图所示。
我们打开执行器管理,在这里新增执行器。在AppName一栏输入我们在执行器配置那里设置的xxl.job.executor.appname。名称自定义即可,注册方式可以选择自动注册,如果执行器配置那配置了执行器的地址,在这里自动注册会自动寻址。如果没有配置地址,这里的地址会是该执行器所在服务器的内网ip+任意一个端口。当然也可以自己手动录入作为执行器的地址。
接下来再打开任务管理,在这里新增任务。选择刚才我们新增好的执行器,调度类型选择CRON,添加好Cron表达式。运行模式选择BEAN,JobHandler选择我们在配置的定时任务里面的Handler名称,其他的自定义填写即可。
然后在操作一栏启动定时任务即可。
8、我配置部署xxl-job遇到的三大神坑
①、xxl-job-core这个包里面的类在初始化的时候会扫描maven项目上所有的API
一开始我万事俱备只欠重启,结果重启后发现服务一直启不来,一直报Bean注册失败的错误,最主要是这个报错的Api这个服务我基本都不使用!
于是我百思不得其解,咋之前就没问题,到现在加了xxl-job就会有问题呢?后面我想把这个无用的Api的maven依赖去除,结果又引出一大堆ClassNotFound,有部分代码引用了这个包里的其他类,这个方法不行!
后面我询问了一个大佬,他说这个问题是因为XxlJobSpringExecutor里的这行代码扫描maven用到的所有依赖导致的,因为jar包里的本来都是懒加载,需要的时候才会用。但是这里拿到定义信息后,就直接初始化了,就导致本来不会报错的就出问题了。
所以我们只需要在我们拉取下来的git代码的xxl-job-core里这个方法上添加一行这样的代码,再使用maven打包成jar包,引入我们项目即可。
//跳过定义的api
if (beanDefinitionName.contains("api")){
continue;
}
②、调度日志里有数据 ,但是运行报表里数据一直为空的问题
部署好调度中心和执行器后,运行了几次任务,在调度日志里都能查询到相关信息,但是运行报表里数据一直都是空的。
我就很奇怪,就看了看运行报表这块的代码,发现就是Oracle和MySQL的差异问题。这块代码查询出来MySQL是驼峰命名,但是Oracle是全部大写的。
所以在下面这块Map里,就全是大写。你用驼峰命名的参数去获取对应的值就是为null,所以运行报表一直为空。
所以只需要根据自己所用的数据库做一下调整即可。比如我使用的是Oracle,那我就把获取值的参数改成大写即可。
Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo);
if (triggerCountMap!=null && triggerCountMap.size()>0) {
int triggerDayCount = triggerCountMap.containsKey("TRIGGERDAYCOUNT")?Integer.valueOf(String.valueOf(triggerCountMap.get("TRIGGERDAYCOUNT"))):0;
int triggerDayCountRunning = triggerCountMap.containsKey("TRIGGERDAYCOUNTRUNNING")?Integer.valueOf(String.valueOf(triggerCountMap.get("TRIGGERDAYCOUNTRUNNING"))):0;
int triggerDayCountSuc = triggerCountMap.containsKey("TRIGGERDAYCOUNTSUC")?Integer.valueOf(String.valueOf(triggerCountMap.get("TRIGGERDAYCOUNTSUC"))):0;
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
xxlJobLogReport.setRunningCount(triggerDayCountRunning);
xxlJobLogReport.setSucCount(triggerDayCountSuc);
xxlJobLogReport.setFailCount(triggerDayCountFail);
}
③、执行任务报连接错误,xxl-job remoting error(Connection reset)
最后我啥都配置好了,但是执行任务就是报连接错误。触发调度说我执行器和调度中心连接不上。
一开始我以为是我配置的端口问题,于是对两台服务器一顿操作,查看端口有没有开放,查看两个服务器能不能ping通,使用nginx来代理执行器地址来让两个调度中心和执行器联通,最后还把调度中心迁移到执行器所在的那个服务器。但结果就是:都不行!就是连接失败!
这时候我才意识到,不是接口问题。毕竟都同一台服务器了,端口也都是开放的,不可能存在连接问题。后面我查看执行器的日志,才发现报netty的错。这个时候我才意识到,是项目的netty-jar包版本问题。
调度中心使用的是xxl-job上面的maven拉取最新的netty版本。而执行器使用的是我项目里的老netty版本。两边版本不一致,所以就导致了连接问题。最后我把项目里的netty版本更新到和调度中心的netty一致的版本,连接成功! 任务也执行成功!