一、@Scheduled注解实现的定时任务
要实现计划任务,首先通过在配置类注解@EnableScheduling来开启对计划任务的支持,然后在要执行计划任务的方法上注解@Scheduled,声明这是一个计划任务。
在Spring Boot 的入口类 XXXApplication 中,必然会有@SpringBootApplication注解,用来标注项目入口,以及完成一些基本的自动自动配置。所以Spring Boot 的项目,可以在启动类中注解@EnableScheduling。
配置类:
计划任务执行类:
其中Scheduled注解中有以下几个参数:
1.cron是设置定时执行的表达式,如 0 0/5 * * * ?每隔五分钟执行一次 秒 分 时 天 月,cron表达式支持使用占位符
2.zone表示执行时间的时区
3.fixedDelay 和fixedDelayString 表示一个固定延迟时间执行,上个任务完成后,延迟多长时间执行
4.fixedRate 和fixedRateString表示一个固定频率执行,上个任务开始后,多长时间后开始执行
5.initialDelay 和initialDelayString表示一个初始延迟时间,第一次被调用前延迟的时间
如果多个定时任务定义的是同一个时间,会根据程序加载标有 @Scheduled 方法的先后来执行。若某个定时任务一直无法执行完成,则无法设置下次任务执行时间,之后会导致此任务后面的所有定时任务无法继续执行,也就会出现所有的定时任务罢工的现象。所以应用SpringBoot 的定时任务的方法中,一定不要出现“死循环”、“执行耗费大量时间”、“http持续等待无响应”的现象,否则会导致定时任务直接罢工。针对数据量、查询或者远程调用特别多的场景,推荐把定时任务分段处理。
优点:
不需要依赖外部框架。
简单快速实现任务。@EnableScheduling、@Scheduled 注解
缺点:
无法管理任务。要停止某个任务,必须重新发布。
不支持动态调整。修改任务参数需要重启项目。
不支持集群方式部署。集群模式下会出现任务多次被调度执行的情况,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行。
单体,即一个项目部署在一台服务器上;
集群,即将单体复制多份部署在多台服务器,其中每个单体被称为一个节点。
二、xxl-job实现的定时任务
1、xxl-job的使用
1. 简介
XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下:
xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用;
xxl-job-executor-sample-frameless:无框架版本;
在xxl-job中,有2个角色:
xxl-job-admin调度中心: 统一管理任务调度平台上的调度任务,负责触发调度执行,并且提供任务管理平台。
xxl-job-executor执行器: 执行器通常是我们的业务系统,如示例中的springboot项目。
设计思想:
将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性。
xxl-job就是一个中心化管理系统,系统主要通过MySQL管理各种定时任务信息,当到了定时任务的触发时间,就把任务信息从数据库中拉进内存,对任务执行器发起调度请求。
2. 使用
①下载源码 : https://github.com/xuxueli/xxl-job/
②初始化“调度数据库”:
到官网下载项目源码并解压,获取 “调度数据库初始化SQL脚本” 并执行即可,正常情况下应该生成16张表。SQL脚本位置:/xxl-job/doc/db/tables_xxl_job.sql
表名 | 作用 |
---|---|
xxl_job_group | 执行器信息表:维护任务执行器信息 |
xxl_job_info | 调度扩展信息表:用于保存xxl-job调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等 |
xxl_job_lock | 任务调度锁表 |
xxl_job_log | 调度日志表:用于保存xxl-job调度任务的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等 |
xxl_job_log_report | 调度日志报表:用户存储xxl-job任务调度日志的报表,调度中心报表功能页面会用到 |
xxl_job_logglue | 任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能 |
xxl_job_registry | 执行器注册表:维护在线的执行器和调度中心机器地址信息 |
xxl_job_user | 系统用户表 |
注意⚠️:调度中心支持集群部署,集群情况下各节点务必连接同一个mysql实例;
如果mysql做主从,调度中心集群节点务必强制走主库
步骤一:调度中心(xxl-job-admin)配置、部署等
调度中心项目: xxl-job-admin
作用: 统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。
调度中心配置文件地址: /xxl-job/xxl-job-admin/src/main/resources/xxl-job-admin.properties
### 调度中心JDBC链接
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl-job?Unicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root_pwd
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
### 报警邮箱
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=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
### 登录账号
xxl.job.login.username=admin
xxl.job.login.password=123456
### 调度中心通讯TOKEN,用于调度中心和执行器之间的通讯进行数据加密,非空时启用
xxl.job.accessToken=
### 调度中心国际化设置,默认为中文版本,值设置为“en”时切换为英文版本
xxl.job.i18n=
启动:部署项目
如果已经正确进行上述配置,可将项目编译打包部署。 调度中心访问地址:http://localhost:8080/xxl-job-admin (该地址执行器将会使用到,作为回调地址),登录后运行界面如下图所示 默认登录账号 “admin/123456”
至此“调度中心”项目已经部署成功。
调度中心集群:
调度中心支持集群部署,提升调度系统容灾和可用性。
调度中心集群部署时,几点要求和建议:
- DB配置保持一致;
- 登陆账号配置保持一致;
- 集群机器时钟保持一致(单机集群忽视);
- 建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
步骤二. xxl-job-executor执行器 在项目中的配置使用
xxl-job-excutor是任务的执行单元,需要在业务系统中实现。
步骤一:在你的项目里引入xxl-job-core的依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.0.1</version>
</dependency>
步骤二:在项目的配置文件中添加
web和执行器端口号不同,调度中心地址addresses和执行器应用名appname保持一致
### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-athena
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志保存天数 [选填] :值大于3时生效,启用执行器Log文件定期清理功能,否则不生效;
xxl.job.executor.logretentiondays=-1
步骤三:添加XxlJobConfig配置类:
在XxljobConfig中初始化一个XxlJobSpringExecutor,该类用于处理xxl-job-admin和xxl-job-excutor之间的通讯以及任务的处理。
package com.xxl.job.executor.core.config;
import com.xxl.job.core.executor.XxlJobExecutor;
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Configuration
@ComponentScan(basePackages = "com.xxl.job.executor.service.jobhandler")
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
xxlJobExecutor.setAdminAddresses(adminAddresses);
xxlJobExecutor.setAppName(appName);
xxlJobExecutor.setIp(ip);
xxlJobExecutor.setPort(port);
xxlJobExecutor.setAccessToken(accessToken);
xxlJobExecutor.setLogPath(logPath);
xxlJobExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobExecutor;
}
}
步骤四:在项目中创建任务
1、继承”IJobHandler”:“com.xxl.job.core.handler.IJobHandler”;
2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
3、注册到执行器工厂:添加“@JobHandler(value=”自定义jobhandler名称”)”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
@JobHandler(value="jobtest")
@Component
public class job_test extends IJobHandler {
@Override
public ReturnT<String> execute(String s) throws Exception {
try {
System.out.println("测试~~~");
/*测试数据*/
return SUCCESS;
} catch (Exception e){
e.printStackTrace();
return FAIL;
}
}
}
步骤五:执行器项目集群(可选)
执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
执行器集群部署时,几点要求和建议:
- 执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作
- 同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表
步骤三. 使用调度中心
1、执行器管理
注意:当是自动注册的时候,会根据appname寻找执行器机器地址,此时Appname 必须和 application.properties 执行器配置文件中appname 保持一致.
如果手动注册,appname 可以不一致,但是机器地址必须和xxl.job.executor.ip 保持一致.
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip=192.168.21.168
xxl.job.executor.port=9999
执行器属性说明
AppName: 是每个执行器集群的唯一标示AppName, 执行器会周期性以AppName为对象进行自动注册。可通过该配置自动发现注册成功的执行器, 供任务调度时使用;
名称: 执行器的名称, 因为AppName限制字母数字等组成,可读性不强, 名称为了提高执行器的可读性;
排序: 执行器的排序, 系统中需要执行器的地方,如任务新增, 将会按照该排序读取可用的执行器列表;
注册方式:调度中心获取执行器地址的方式;
自动注册:执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址;
手动录入:人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用;
机器地址:”注册方式”为”手动录入”时有效,支持人工维护执行器的地址信息;
2、任务管理
点击新增任务后创建任务
jobhandler里的内容需要与任务代码中@jobhandler的value 保持一致.
运行模式:
- BEAN模式:支持基于类的开发方式,每个任务对应一个Java类。
- GLUE模式:任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。
路由策略: 集群模式下某个任务选择由哪个执行器完成的策略。
3、日志管理,执行任务后,在调度中心可以查看调度日志。
调度时间:”调度中心”触发本次调度并向”执行器”发送任务执行信号的时间;
调度结果:”调度中心”触发本次调度的结果,200表示成功,500或其他表示失败;
调度备注:”调度中心”触发本次调度的日志信息;
执行器地址:本次任务执行的机器地址
运行模式:触发调度时任务的运行模式,运行模式可参考章节 “三、任务详解”;
任务参数:本地任务执行的入参
执行时间:”执行器”中本次任务执行结束后回调的时间;
查看两个临近的调度结果,可以发现任务是由两个执行器轮询执行的。