一、传统的定时任务
1. 传统的定时任务存在那些缺点2. 定时任务集群幂等性问题 二、传统定时任务的实现方案
2.1. 多线程2.2. Timetask2.3. 线程池2.4. SpringBoot注解形式2.5. 基于Quartz 三、分布式任务调度平台架构设计原理
3.1. xxl-job项目模块讲解3.2. 最佳实战3.3. Xxl-job定时任务框架实现原理 四、常见面试题
4.1. XXL-JOB与ElasticJob区别之间区别4.2. 分布式任务调度分片集群策略原理4.3. 如何保证任务调度平台高可用问题
XXL开源社区
传统任务调度存在的缺陷
1.业务逻辑与定时任务逻辑放入在同一个Jar包中,如果定时任务逻辑挂了也会影响到业务逻辑;2.如果服务器集群的情况下,可能存在定时任务逻辑会重复触发执行;3.定时任务执行非常消耗cpu的资源,可能会影响到业务线程的执行 2. 定时任务集群幂等性问题
定时任务集群,如何保证定时任务幂等性问题
如何在集群中,保证我们的定时任务只会触发一次
1.将业务逻辑和定时任务逻辑完全分开部署,实现解耦、只对业务逻辑实现集群,不对我们的定时任务逻辑集群;—定时任务单机版本 缺点无法实现高可用的问题;2.对我们Jar包加上一个开关,项目启动的时候读取该开关 如果为true的情况下则加载定时任务类,否则情况下就不加载该定时任务类;–缺点无法实现高可用的问题;3.在数据库加上一个主键能够创建成功,则触发定时任务,否则就不触发定时任务, 高可用的问题;4.分布式锁实实现,只要jar能够拿到分布式锁就能够执行定时任务,否则情况下不执行;
总结:以上的方案都是属于规模比较小的项目,在微服务架构中应该采用分布式任务调度平台。
二、传统定时任务的实现方案多线程形式、timetask、线程池、springboot注解形式、quartz
2.1. 多线程基于多线程方式实现
package com.gblfy;
public class ThreadTask {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
System.out.println("定时任务触发...");
} catch (InterruptedException e) {
}
}
}
}).start();
}
}
2.2. Timetask
package com.gblfy;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTaskDemo {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "定时任务触发");
}
};
// 天数
long delay = 0;
// 耗秒数
long period = 1000;
new Timer().scheduleAtFixedRate(timerTask, delay, period);
}
}
2.3. 线程池
package com.gblfy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("定时任务触发..");
}
};
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);
}
}
2.4. SpringBoot注解形式
package com.gblfy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class ScheduledTaskApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduledTaskApplication.class, args);
}
}
package com.gblfy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class UserScheduled {
@Scheduled(cron = "0/1 * * * * *")
public void taskUserScheduled() {
System.out.println("定时任务触发...");
}
}
2.5. 基于Quartz
org.springframework.boot spring-boot-starter-web org.quartz-scheduler quartz 2.3.2 org.quartz-scheduler quartz-jobs 2.3.2
MyJob
package com.gblfy.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("quartz MyJob date:" + System.currentTimeMillis());
}
}
QuartzTest
package com.gblfy.job;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
public class QuartzTest {
public static void main(String[] args) throws SchedulerException {
//1.创建Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2.从工厂中获取调度器实例
Scheduler scheduler = sf.getScheduler();
//3.创建JobDetail
JobDetail jb = JobBuilder.newJob(MyJob.class)
.withDescription("this is a ram job") //job的描述
.withIdentity("ramJob", "ramGroup") //job 的name和group
.build();
//任务运行的时间,SimpleSchedle类型触发器有效
long time = System.currentTimeMillis() + 3 * 1000L; //3秒后启动任务
Date statTime = new Date(time);
//4.创建Trigger
//使用SimpleScheduleBuilder或者CronScheduleBuilder
Trigger t = TriggerBuilder.newTrigger()
.withDescription("")
.withIdentity("ramTrigger", "ramTriggerGroup")
//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
.startAt(statTime) //默认当前时间启动
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //两秒执行一次
.build();
//5.注册任务和定时器
scheduler.scheduleJob(jb, t);
//6.启动 调度器
scheduler.start();
}
}
三、分布式任务调度平台架构设计原理
3.1. xxl-job项目模块讲解
xxl-job-admin—分布式任务调度中心平台
xxl-job-core—源码实现部分
xxl-job-executor-samples–执行器项目 定时任务模块项目
执行器模块:(注册中心)存放实际执行我们定时任务项目模块IP和端口信息;
分布式任务调度中心(Nginx):负责所有执行器执行定时任务的分配;
定时任务与业务逻辑实现解耦,分开部署。
定时任务是一个单独的项目。
gblfy-member—会员服务接口
member-job----负责会员服务定时任务
- 当我们的定时任务模块项目启动的时候,会将该ip和端口信息注册到 定时任务注册中心上并发送rest请求需要将定时任务创建在任务调度中心中,关联执行器 定时任务模块实际执行ip和端口号码。创建定时任务会在xxl-job admin 调度中心中项目先触发,从执行器注册中心查找到执行器接口信息,采用路由策略(负载均衡算法)选择一个执行器(定时任务)地址 发送通知执行定时任务。
建议:配置定时任务规则的时候,建议提前5-10s;
常用分布式任务调度框架
Xxl-job、elasticjob、SpringAlibaba Cloud SchedulerX
XXL-Job Admin如何实现集群
XXL-JOB内置定时任务调度中心,ElasticJob借助于zookeeper作为注册中心
4.2. 分布式任务调度分片集群策略原理执行器集群部署时,任务路由策略选择”分片广播”情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
“分片广播” 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
“分片广播” 和普通任务开发流程一致,不同之处在于可以获取分片参数,获取分片参数进行分片业务处理。
4.3. 如何保证任务调度平台高可用问题调度中心集群(可选):
调度中心支持集群部署,提升调度系统容灾和可用性。
调度中心集群部署时,几点要求和建议:
1.DB配置保持一致;
2.集群机器时钟保持一致(单机集群忽视);
建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
执行器集群(可选):
执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
执行器集群部署时,几点要求和建议:
1.执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。2.同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。



