Quartz简介
Quartz代码
try {
//创建调度程序实体类
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
//任务详情
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1","group1")
.build();
//触发器1
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.startNow()
//设置调度策略
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
//调度任务
scheduler.scheduleJob(job,trigger1);
TimeUnit.SECONDS.sleep(3);
//关闭调度任务
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
一般都会创建两个触发器去执行一个任务详情,一个自动触发,一个手动触发
触发器类型
CalendarIntervalTriggerImpl.class
CronTriggerImpl.class
DailyTimeIntervalTriggerImpl.class
SimpleTriggerImpl.class
CronTrigger
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.startNow()
//设置调度策略
.withSchedule(
CronScheduleBuilder.cronSchedule("14/15 * * * * ? *")
)
.build();
cron表达式
, :代表允许多个值 (1,20 * * * * ?*) 每分钟第1秒 第20秒的时候执行
- :代表区间执行 (1-20 * * * * ?*)每分钟第1秒至第20秒区间执行
* :代表每单位执行一次 (* * * * * ?*)每秒执行一次
/ :代表每多长时间执行一次 (0/15 * * * * ?*)在0秒的时候执行并且每15秒一次 (3/15 * * * * ?*)在第3秒的时候执行并且每15秒一次
? :只在日与星期中使用,日期确定,则星期必须?,相反亦是
L :
最后一个(* * * L * ?*)每个月的最后一天的每分每秒 (* * * ?8 3L *):代表8月的最后一个星期二
(* * * L-3 8 * *) 8月的倒数第三天
W :日位置: 15w表示距离15号最近的工作日 1w 如果1是周六,则1最近的工作日只能为未来日期,不能是上个月的工作日
LW:组合表示月最后一个工作日
# : 6#3表示第三个星期五
获取参数
1、将key-value放入jobDataMap中
//任务详情
JobDetail job = JobBuilder.newJob(HelloJob.class)
//usingJobData往 jobDataMap中添加参数
.usingJobData("job-key1", "job-value1")
.withIdentity("job1", "group1")
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
//usingJobData往 jobDataMap中添加参数
.usingJobData("trigger-key1", "trigger-value1")
.withIdentity("trigger1", "group1")
.startNow()
//设置调度策略
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever()
)
.build();
2、获取在执行的任务类中获取JobDataMap中的key-value
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//获取jobDetail中的JobDataMap中的参数
JobDetail jobDetail = context.getJobDetail();
System.out.println(jobDetail.getJobDataMap().get("job-key1"));
//获取trigger中的JobDataMap中的参数
Trigger trigger = context.getTrigger();
System.out.println(trigger.getJobDataMap().get("trigger-key1"));
//context获取的是合并后的jobDataMap,如果jobDetail与trigger中map的key有冲突,则trigger中的值覆盖jobDetail中的值
JobDataMap jobDataMap = context.getMergedJobDataMap();
System.out.println(jobDataMap.get("both-key"));
System.out.println("Hello.Execute:" + DFUtil.Date(new Date())+":"+context.getTrigger().getKey().getName());
}
}
如果有set方法的成员变量的名称与map中的key相同,则启动定时任务,会自动将map中的value值注入到成员变量中
public class HelloJob implements Job {
private String bothKey;
public void setBothKey(String bothKey) {
this.bothKey = bothKey;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//如果有set方法的成员变量的名称与map中的key相同,则启动定时任务,会自动将map中的value值注入到成员变量中
System.out.println(bothKey);
System.out.println("Hello.Execute:" + DFUtil.Date(new Date())+":"+context.getTrigger().getKey().getName());
}
}
quartz.properties
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
如果需要配置properties,则只需要新建quartz.properties文件,修改配置即可生效
可通过api测试配置文件是否生效
//创建调度程序实体类
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
System.out.println(scheduler.getSchedulerName());
System.out.println(scheduler.getMetaData().getThreadPoolSize());
小知识点:
如果想 scheduler.scheduleJob(trigger)
只传参trigger
,需要将job
设置为 storeDurably()
,并且将job
加入到scheduler
中
//任务详情
JobDetail job = JobBuilder.newJob(HelloJob.class)
//usingJobData往 jobDataMap中添加参数
.withIdentity("job", "group1")
.storeDurably()
.build();
Trigger trigger = TriggerBuilder.newTrigger()
//usingJobData往 jobDataMap中添加参数
.withIdentity("trigger", "group1")
.startNow()
.forJob(job)
//设置调度策略
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever()
)
.build();
//调度任务
scheduler.addJob(job,false);
scheduler.scheduleJob(trigger);
spring集成Quartz
使用Quartz
1、初始化 Scheduler 对象
@Component
public class InitJob {
//将Scheduler 注入容器,可在spring项目中任意位置获取
@Autowired
private Scheduler scheduler;
@PostConstruct
public void initJob() throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(SpringBootJob.class)
.withIdentity("job")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger")
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
2、执行任务继承 QuartzJobBean 类,此类是spring实现quartz中的job接口的实现类
public class SpringBootJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("我是SpringBoot"+sdf.format(new Date()));
}
}
3、启动springBootApplication即可
持久化
quartz默认是使用内存存储定时任务数据的,如果宕机或者长时间运行多个job,可能会造成内存溢出;项目中都会将quartz进行持久化,将数据存到数据库中的quartz表中
springboot持久化
第一种:quartz表与业务表在一个数据库中
配置datasource参数,quartz参数
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.168.132:3306/db01?useUnicode=true&characterEncoding=utf-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
quartz: #springboot集成quartz的数据库持久化
jdbc:
initialize-schema: always #每次重启总是重新渐离quartz的表
job-store-type: jdbc #设置quartz数据持久化到jdbc数据库中
properties: #此属性下可以配置quartz其他未被springboot设置的属性
org.quartz.threadPool.threadCount: 10
启动之后在数据库中会自动新建quartz的几张表
第二种:quartz与业务数据分开配置数据库
只需要在配置中配置一个DataSource的bean对象,并在对象上加上注解@QuartzDataSource
@Configuration
public class BeanConfig {
@Bean
@QuartzDataSource
public DataSource getDataSource(){
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setURL("jdbc:mysql://192.168.168.132:3306/db02?useUnicode=true&characterEncoding=utf-8");
return dataSource;
}
}
yml配置不变
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.168.132:3306/db01?useUnicode=true&characterEncoding=utf-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
quartz: #springboot集成quartz的数据库持久化
jdbc:
initialize-schema: always #每次重启总是重新渐离quartz的表
job-store-type: jdbc #设置quartz数据持久化到jdbc数据库中
properties: #此属性下可以配置quartz其他未被springboot设置的属性
org.quartz.threadPool.threadCount: 10
启动之后,quartz会在第二个数据库中生成quartz的表
集群
quartz集群的两种方式:
1、只让集群中的一台去跑定时任务,其他机器性能浪费
2、集群中的每台服务器都会去执行定时任务,但是一个任务只会分配到集群中的一台服务器中执行
只要几台服务器连接的都是同样的quartz数据库,则这几台机器执行定时任务都是从数据库中拿数据,默认轮询执行任务
例如:a、b、c三台服务器,定时任务每一秒打印一次数据库名称,启动三台服务器;
a服务器在第一秒打印执行任务,数据持久化到数据库中;
b服务器从数据库中获取数据,领取第2秒时的任务,b在第2秒打印,数据持久化到数据库;
c服务器同样 在数据库中获取到数据,领取第3秒的任务,在第3秒的时候打印;
在a后台出现的就是1秒打印 4秒打印 7秒打印
在b后台出现的就是2秒打印 5秒打印 8秒打印
在c后台出现的就是3秒打印 6秒打印 9秒打印
一台主机模拟两个服务器运行
1、新建两个yml
2、配置yml 集群属性
spring:
quartz:
properties:
org.quartz.scheduler.instanceName: OrderService #集群名称
org.quartz.scheduler.instanceId: Order-1 #集群中的标识
org.quartz.jobStore.isClustered: true #开启集群模式
3、配置启动环境
4、启动1、2、服务器实例,后台打印显示是集群模式
如果是集群模式的话,初始化任务的时候可能会报错
解决方案:
1、使用Controller去初始化任务到数据库中推荐
@Component
public class InitClusterJob {
@Autowired
private Scheduler scheduler;
@Autowired
private QuartzMapper mapper;
@PostConstruct
public void initJob() throws SchedulerException {
/**
* 任务初始化,如果是集群的情况,可能会造成任务初始化的时候出现重复任务报错
*提供一种思路:将这段代码放到controller中去执行,发送请求调用controller将任务初始化到数据库中init_202008Controller()
* 每次增加定时任务,都可以将新增的定时任务,写道一个新的controller中去进行初始化
*/
startSpringJob("job_1","trigger_1");
startSpringJob("job_2","trigger_2");
startSpringJob("job_3","trigger_3");
}
private void startSpringJob(String jobName,String triggerName)throws SchedulerException{
JobDetail jobDetail = JobBuilder.newJob(SpringBootJob.class)
.withIdentity(jobName)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName)
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
2、使用 scheduler.scheduleJob(jobDetail,set,true) 方法 不推荐
//参数Set集合中放Triger,true表示当数据库中的任务与当前任务名有冲突时,覆盖掉数据库里的任务
Set<Trigger> set =new HashSet<>();
set.add(trigger);
scheduler.scheduleJob(jobDetail,set,true);
场景:如果有20台订单业务服务器,一共10000个定时任务,为了分担quartz数据库的压力,将quartz分成两个数据库,每10台订单服务器组成一个集群,即:集群1 集群2
设计思路:
1、配置两个集群的配置文件
spring:
quartz:
properties:
org.quartz.scheduler.instanceName: OrderService-1 #集群名称
org.quartz.scheduler.instanceId: Order-1 #集群中的标识
org.quartz.jobStore.isClustered: true #开启集群模式
spring:
quartz:
properties:
org.quartz.scheduler.instanceName: OrderService-2 #集群名称
org.quartz.scheduler.instanceId: Order-1 #集群中的标识
org.quartz.jobStore.isClustered: true #开启集群模式
2、
在代码中获取yml中集群名称,与任务要放入的集群名称相匹配,如果一致则加载任务
@Component
public class InitClusterJob {
@Autowired
private Scheduler scheduler;
@Value("${spring.quartz.properties.org.quartz.scheduler.instanceName}")
private String instanceName;
@PostConstruct
public void initJob() throws SchedulerException, ParseException {
//OrderService-1 要哪个集群去执行
startSpringJob("job_1", "trigger_1","OrderService-1");
startSpringJob("job_2", "trigger_2","OrderService-2");
startSpringJob("job_3", "trigger_3","OrderService-3");
}
private void startSpringJob(String jobName, String triggerName, String partition) throws SchedulerException {
//判断当前服务器属于哪个集群,根据集群判断是否加载任务;
if (!instanceName.equals(partition)) {
//如果当前集群名称与任务要求执行集群的名称一致,则加载任务
JobDetail jobDetail = JobBuilder.newJob(SpringBootJob.class)
.withIdentity(jobName)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName)
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
}
分布式
就是多个独立的业务组件,链接一个quartz数据库,没什么特殊配置,每个组件设置自己的集群名称即可