文章目录
- 一、传统的定时任务
- 1. 传统的定时任务存在那些缺点
- 2. 分布式任务调度
- 3. 定时任务集群幂等性问题
- 二、传统定时任务的实现方案
- 2.1. 多线程
- 2.2. TimeTask
- 2.3. 线程池
- 2.4. SpringBoot注解形式
- 2.5. 基于Quartz
- 三、常⻅分布式定时任务
- 3.1. Quartz
- 3.2. TBSchedule
- 3.3. Elastic-job
- 3.4. XXL-JOB
- 四、分布式任务调度平台架构设计原理
- 4.1. xxl-job项目模块讲解
- 4.2. 最佳实战
- 4.3. Xxl-job定时任务框架实现原理
- 五、常见面试题
- 5.1. XXL-JOB与ElasticJob区别之间区别
- 5.2. 分布式任务调度分片集群策略原理
- 5.3. 如何保证任务调度平台高可用问题
- 六、XXL-Job集群部署和⾼可⽤最佳实战
XXL开源社区
一、传统的定时任务
1. 传统的定时任务存在那些缺点
传统任务调度存在的缺陷
- 1.业务逻辑与定时任务逻辑放入在同一个Jar包中,如果定时任务逻辑挂了也会影响到业务逻辑;
- 2.如果服务器集群的情况下,可能存在定时任务逻辑会重复触发执行;
- 3.定时任务执行非常消耗cpu的资源,可能会影响到业务线程的执行
2. 分布式任务调度
分布式集群的模式下,如果采用集中式的任务调度方式,会带来一些问题,比如
- 1、多台机器集群部署的定时任务如何保证不被重复执行?
- 2、如何动态地调整定时任务的执行时间?(不重启服务的情况)
- 3、部署定时任务的机器发生故障如何实现故障转移?
- 4、如何对定时任务进行监控、告警、高可用?
- 5、定时任务统一调度管理,和追踪各个的服务节点之间任务调度的结
果,并保存记录任务信息等等
3. 定时任务集群幂等性问题
定时任务集群,如何保证定时任务幂等性问题
如何在集群中,保证我们的定时任务只会触发一次
- 1.将业务逻辑和定时任务逻辑完全分开部署,实现解耦、只对业务逻辑实现集群,不对我们的定时任务逻辑集群;—定时任务单机版本 缺点无法实现高可用的问题;
- 2.对我们Jar包加上一个开关,项目启动的时候读取该开关 如果为true的情况下则加载定时任务类,否则情况下就不加载该定时任务类;–缺点无法实现高可用的问题;
- 3.在数据库加上一个主键能够创建成功,则触发定时任务,否则就不触发定时任务, 高可用的问题;
- 4.分布式锁实实现,只要jar能够拿到分布式锁就能够执行定时任务,否则情况下不执行;
总结:以上的方案都是属于规模比较小的项目,在微服务架构中应该采用分布式任务调度平台。
二、传统定时任务的实现方案
多线程形式、timetask、线程池(ScheduledExecutorService)、SpringBoot注解形式(EnableScheduling +@Scheduled底层依然是采用 Spring Task )、quartz
2.1. 多线程
基于多线程方式实现
package com.gblfy;public class ThreadTask {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- quartz --><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.2</version></dependency><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz-jobs</artifactId><version>2.3.2</version></dependency>
MyJob
package com.gblfy.job;import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class MyJob implements Job {@Overridepublic 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.创建JobDetailJobDetail 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或者CronScheduleBuilderTrigger 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. Quartz
Quartz关注点在于定时任务⽽并⾮是数据,并没有⼀套根据数据化处理⽽定的流程
虽然可以实现数据库作业的⾼可⽤,但是缺少了分布式的并⾏调度功能,相对弱点
不⽀持任务分⽚、没UI界⾯管理,并⾏调度、失败策略等也缺少
3.2. TBSchedule
这个是阿⾥早期开源的分布式任务调度系统,使⽤的是timer⽽不是线程池执⾏任务调度,使⽤timer在处理异常的时候是有缺陷的,但TBSchedule的作业类型⽐较单⼀,⽂档也缺失得⽐较严重
⽬前阿⾥内部使⽤的是ScheduleX
阿⾥云也有商业化版本: https://help.aliyun.com/product/147760.html
3.3. Elastic-job
当当开发的分布式任务调度系统,功能强⼤,采⽤的是zookeeper实现分布式协调,具有⾼可⽤与分⽚。
2020年6⽉,ElasticJob的四个⼦项⽬已经正式迁⼊Apache仓库由 2 个相互独⽴的⼦项⽬ ElasticJob-Lite 和 ElasticJobCloud 组成ElasticJob-Lite 定位为轻量级⽆中⼼化解决⽅案,使⽤jar的形式提供分布式任务的协调服务;ElasticJob-Cloud 使⽤ Mesos 的解决⽅案,额外提供资源治理、应⽤分发以及进程隔离等服务
地址:https://shardingsphere.apache.org/elasticjob/index_zh.html
3.4. XXL-JOB
⼤众点评的员⼯徐雪⾥在15年发布的分布式任务调度平
台,是轻量级的分布式任务调度框架,⽬标是开发迅速、
简单、清理、易扩展; ⽼版本是依赖quartz的定时任务触
发,在v2.1.0版本开始 移除quartz依赖
地址:https://www.xuxueli.com/xxl-job
四、分布式任务调度平台架构设计原理
4.1. xxl-job项目模块讲解
xxl-job-admin—分布式任务调度中心平台
xxl-job-core—源码实现部分
xxl-job-executor-samples–执行器项目 定时任务模块项目
执行器模块:(注册中心)存放实际执行我们定时任务项目模块IP和端口信息;
分布式任务调度中心(Nginx):负责所有执行器执行定时任务的分配;
4.2. 最佳实战
定时任务与业务逻辑实现解耦,分开部署。
定时任务是一个单独的项目。
gblfy-member—会员服务接口
member-job----负责会员服务定时任务
4.3. Xxl-job定时任务框架实现原理
- 当我们的定时任务模块项目启动的时候,会将该ip和端口信息注册到 定时任务注册中心上并发送rest请求
- 需要将定时任务创建在任务调度中心中,关联执行器 定时任务模块实际执行ip和端口号码。
- 创建定时任务会在xxl-job admin 调度中心中项目先触发,从执行器注册中心查找到执行器接口信息,采用路由策略(负载均衡算法)选择一个执行器(定时任务)地址 发送通知执行定时任务。
建议:配置定时任务规则的时候,建议提前5-10s;
常用分布式任务调度框架
Xxl-job、elasticjob、SpringAlibaba Cloud SchedulerX
XXL-Job Admin如何实现集群
五、常见面试题
5.1. XXL-JOB与ElasticJob区别之间区别
XXL-JOB内置定时任务调度中心,ElasticJob借助于zookeeper作为注册中心
对⽐项 | XXL-JOB | elastic-job |
---|---|---|
并⾏调度 | 调度系统多线程并⾏ | 任务分⽚的⽅式并⾏ |
弹性扩容 | 使⽤Quartz基于数据库分布式功能 | 通过zookeeper保证 |
⾼可⽤ | 通过DB锁保证 | 通过zookeeper保证 |
阻塞策略 | 单机串⾏/丢弃后续的调度/覆盖之前的调度 | 执⾏超过zookeeper的session timeout 时间的话,会被清除,重新进⾏分⽚ |
动态分⽚策略 | 以执⾏器为维度进⾏分⽚、⽀持动态的扩容 | 平均分配/作业名hash分配/⾃定义策略 |
失败处理策略 | 失败告警/失败重试 | 执⾏完毕后主动获取未分配分⽚任务, 服务器下线后主动寻找可以⽤的服务器执⾏任务 |
监控 | ⽀持 | ⽀持 |
⽇志 | ⽀持 | ⽀持 |
5.2. 分布式任务调度分片集群策略原理
执行器集群部署时,任务路由策略选择”分片广播”情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
“分片广播” 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
“分片广播” 和普通任务开发流程一致,不同之处在于可以获取分片参数,获取分片参数进行分片业务处理。
5.3. 如何保证任务调度平台高可用问题
调度中心集群(可选):
调度中心支持集群部署,提升调度系统容灾和可用性。
调度中心集群部署时,几点要求和建议:
-
1.DB配置保持一致;
-
2.集群机器时钟保持一致(单机集群忽视);
-
建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
执行器集群(可选):
执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
执行器集群部署时,几点要求和建议:
- 1.执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。
- 2.同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。
六、XXL-Job集群部署和⾼可⽤最佳实战
XXL-Job集群部署和⾼可⽤最佳实战