定时任务需求可以使用以下技术:(定时器)
quartz:可以使用集群,使用不方便
spring-task:不能使用集群,使用方便,一个注解完事
xxl-job定时任务,中国人写的
elastic-job 开源的,功能多
Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。
官网:http://www.quartz-scheduler.org/
maven坐标:
<!--quartz的基础包-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
<!--spring整合Quartz-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
需要 配置自定义Job、任务描述、触发器、调度工厂
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="jobDemo" class="com.itheima.job.JobDemo">bean>
<bean id="jobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="jobDemo"/>
<property name="targetMethod" value="run"/>
bean>
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<property name="cronExpression">
<value>0/10 * * * * ?value>
property>
bean>
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="myTrigger"/>
list>
property>
bean>
beans>
这个我一般去网上查https://cron.qqe2.com/
cron表达式分为七个域,之间使用空格分隔。其中最后一个域(年)可以为空。每个域都有自己允许的值和一些特殊字符构成。使用这些特殊字符可以使我们定义的表达式更加灵活。
下面是对这些特殊字符的介绍:
逗号(,):指定一个值列表,例如使用在月域上1,4,5,7表示1月、4月、5月和7月
横杠(-):指定一个范围,例如在时域上3-6表示3点到6点(即3点、4点、5点、6点)
星号(*):表示这个域上包含所有合法的值。例如,在月份域上使用星号意味着每个月都会触发
斜线(/):表示递增,例如使用在秒域上0/15表示每15秒
问号(?):只能用在日和周域上,但是不能在这两个域上同时使用。表示不指定
井号(#):只能使用在周域上,用于指定月份中的第几周的哪一天,例如6#3,意思是某月的第三个周五 (6=星期五,3意味着月份中的第三周)
L:某域上允许的最后一个值。只能使用在日和周域上。当用在日域上,表示的是在月域上指定的月份的最后一天。用于周域上时,表示周的最后一天,就是星期六
W:W 字符代表着工作日 (星期一到星期五),只能用在日域上,它用来指定离指定日的最近的一个工作日
java代码中注解方式实现
1.定义 Task 类
/**
* Spring Scheduled示例
*/
@Component
public class ScheduledTask {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Integer count0 = 1;
private Integer count1 = 1;
private Integer count2 = 1;
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() throws InterruptedException {
System.out.println(String.format("reportCurrentTime第%s次执行,当前时间为:%s", count0++, dateFormat.format(new Date())));
}
@Scheduled(fixedDelay = 5000)
public void reportCurrentTimeAfterSleep() throws InterruptedException {
System.out.println(String.format("reportCurrentTimeAfterSleep第%s次执行,当前时间为:%s", count1++, dateFormat.format(new Date())));
}
@Scheduled(cron = "0 0 1 * * *")
public void reportCurrentTimeCron() throws InterruptedException {
System.out.println(String.format("reportCurrentTimeCron第%s次执行,当前时间为:%s", count2++, dateFormat.format(new Date())));
}
}
2.启动定时任务
在Spring Boot的主类中加入@EnableScheduling注解,启用定时任务的配置
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@EnableScheduling
public class ScheduledTaskTests {
@Test
public void test() {
log.info("启动了ScheduledTask定时作业");
while (true) {
}
}
}
上面的是简单的使用
quartz实现分布式定时任务
quartz 是一个开源的分布式调度库,它基于java实现。
> 它有着强大的调度功能,支持丰富多样的调度方式,比如简单调度,基于cron表达式的调度等等。
> 支持调度任务的多种持久化方式。比如支持内存存储,数据库存储,Terracotta server 存储。
> 支持分布式和集群能力。
> 采用JDBCJobStore方式存储时,针对事务的处理方式支持全局事务(和业务服务共享同一个事务)和局部事务(quarzt 单独管理自己的事务)
> 基于plugin机制以及listener机制支持灵活的扩展。
1.pom.xml配置
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-quartzartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
2.spring-quartz.properties集群配置
#============================================================================
# 配置JobStore
#============================================================================
# JobDataMaps是否都为String类型,默认false
org.quartz.jobStore.useProperties=false
# 表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix = QRTZ_
# 是否加入集群
org.quartz.jobStore.isClustered = true
# 调度实例失效的检查时间间隔 ms
org.quartz.jobStore.clusterCheckinInterval = 5000
# 当设置为“true”时,此属性告诉Quartz 在非托管JDBC连接上调用setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED)。
org.quartz.jobStore.txIsolationLevelReadCommitted = true
# 数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#============================================================================
# Scheduler 调度器属性配置
#============================================================================
# 调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName = ClusterQuartz
# ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceId= AUTO
#============================================================================
# 配置ThreadPool
#============================================================================
# 线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 指定线程数,一般设置为1-100直接的整数,根据系统资源配置
org.quartz.threadPool.threadCount = 5
# 设置线程的优先级(可以是Thread.MIN_PRIORITY(即1)和Thread.MAX_PRIORITY(这是10)之间的任何int 。默认值为Thread.NORM_PRIORITY(5)。)
org.quartz.threadPool.threadPriority = 5
3.定义两个job
QuartzJob.java
//持久化
@PersistJobDataAfterExecution
//禁止并发执行(Quartz不要并发地执行同一个job定义(这里指一个job类的多个实例))
@DisallowConcurrentExecution
@Slf4j
public class QuartzJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String taskName = context.getJobDetail().getJobDataMap().getString("name");
log.info("---> Quartz job {}, {} <----", new Date(), taskName);
}
}
QuartzJob2.java
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Slf4j
public class QuartzJob2 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String taskName = context.getJobDetail().getJobDataMap().getString("name");
log.info("---> Quartz job 2 {}, {} <----", new Date(), taskName);
}
}
4.初始化触发器等信息,这里通过Listener初始化
@Slf4j
public class StartApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
SchedulerConfig schedulerConfig;
public static AtomicInteger count = new AtomicInteger(0);
private static String TRIGGER_GROUP_NAME = "test_trriger";
private static String JOB_GROUP_NAME = "test_job";
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 防止重复执行
if (event.getApplicationContext().getParent() == null && count.incrementAndGet() <= 1) {
initMyJob();
}
}
public void initMyJob() {
Scheduler scheduler = null;
try {
scheduler = schedulerConfig.scheduler();
TriggerKey triggerKey = TriggerKey.triggerKey("trigger1", TRIGGER_GROUP_NAME);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (null == trigger) {
Class clazz = QuartzJob.class;
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity("job1", JOB_GROUP_NAME).build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", TRIGGER_GROUP_NAME)
.withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
log.info("Quartz 创建了job:...:{}", jobDetail.getKey());
} else {
log.info("job已存在:{}", trigger.getKey());
}
TriggerKey triggerKey2 = TriggerKey.triggerKey("trigger2", TRIGGER_GROUP_NAME);
CronTrigger trigger2 = (CronTrigger) scheduler.getTrigger(triggerKey2);
if (null == trigger2) {
Class clazz = QuartzJob2.class;
JobDetail jobDetail2 = JobBuilder.newJob(clazz).withIdentity("job2", JOB_GROUP_NAME).build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/15 * * * * ?");
trigger2 = TriggerBuilder.newTrigger().withIdentity("trigger2", TRIGGER_GROUP_NAME)
.withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail2, trigger2);
log.info("Quartz 创建了job:...:{}", jobDetail2.getKey());
} else {
log.info("job已存在:{}", trigger2.getKey());
}
scheduler.start();
} catch (Exception e) {
log.info(e.getMessage());
}
}
}
5.启动定时器
启动两个Application,分别是示例中的DemoQuartzApplication和DemoQuartzApplication2,会发现,两个Job会分别在两个应用执行。
当手动停止一个应用的时候,另一个应用会自动接管所有任务并继续执行,如果任务太多,我们可以再开一台服务即可。实现了调度任务的高可用性和可扩展性