文章目录
1. 概念
1. 定时任务的基本概念
程序为解决一个信息处理任务而预先编制的工作执行方案,这就是定时任务,核心组成如下:
- 执行器:负责管理应用运行时环境,用于调度定时任务。
- 任务:任务执行的流程,是一个类,具体的业务。
- 触发器:按照某种时间规则,执行具体的调度任务。
2. 定时任务的使用场景
日常开发中,定时任务主要分为如下两种使用场景:
时间驱动:
- 对账单、日结
- 营销类短信
- MQ定时检查生产失败的消息
数据驱动:
- 异步数据交换
- 数据同步
3. 原生定时任务缺陷有哪些缺陷?
分布式技术应用的时代,原生定时任务的缺陷显得更为突出。结合传统项目与分布式微服务的架构,思考总结如下,欢迎各位大神给与补充:
- 不支持集群多节点部署,需要自己实现避免任务重复执行的问题。
- 不支持分片任务,处理有序数据时,多机器分片执行任务处理不同数据。
- 不支持动态调整,不重启服务的情况下修改任务的参数。
- 没有报警机制,当任务失败后没有报警机制通知。
- 不支持生命周期统一管理,如不重启服务情况下关闭、启动任务。
- 不支持失败重试,出现异常后任务终结,不能根据状态控制任务重新执行。
- 无法统计任务数据,当任务数据量大的时候,对于任务执行情况无法高效的统计执行情况。
4. 基于当前 XXL-JOB 我们能做什么?
- 执行器 HA(分布式):天生支持任务分布式执行,无需自己实现。任务"执行器"支持集群部署,可保证任务执行 HA;
- 调度中心 HA(中心式):调度中心相当于传统调度任务的触发器,调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心 HA;
2. 系统架构和整理流程
https://www.xuxueli.com/xxl-job/
2.1. 设计思想
- 将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
- 将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
- 因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;
2.2. 架构图
2.3. 执行流程
3. 启动流程
3.1. 服务器启动
首先找到配置类 XxlJobAdminConfig, 可以发现该类实现 InitializingBean接口,这里直接看 afterPropertiesSet方法即可。
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
// ---------------------- XxlJobScheduler ----------------------
private XxlJobScheduler xxlJobScheduler;
@Override
public void afterPropertiesSet() throws Exception {
adminConfig = this;
// 初始化xxljob调度器
xxlJobScheduler = new XxlJobScheduler();
xxlJobScheduler.init();
}
...
}
public void init() throws Exception {
// init i18n
initI18n();
// admin trigger pool start
// 初始化触发器线程池
JobTriggerPoolHelper.toStart();
// admin registry monitor run
/**
* 30秒执行一次,维护注册表信息, 判断在线超时时间90s
* 1. 删除90s未有心跳的执行器节点;jobRegistry
* 2. 获取所有的注册节点,更新到jobGroup(执行器)
*/
JobRegistryHelper.getInstance().start();
// admin fail-monitor run 运行事变监视器,主要失败发送邮箱,重试触发器
JobFailMonitorHelper.getInstance().start();
// admin lose-monitor run ( depend on JobTriggerPoolHelper )
// 将丢失主机调度日志置为失败
JobCompleteHelper.getInstance().start();
// admin log report start 统计一些失败成功报表
JobLogReportHelper.getInstance().start();
// start-schedule ( depend on JobTriggerPoolHelper )
/**
* 调度器执行任务(两个线程 + 线程池执行调度逻辑)
* 1. 调度线程50s执行一次;查询5s秒内执行的任务,并按照不同逻辑执行
* 2. 时间轮线程每1秒执行一次;时间轮算法,并向前跨一个时刻;
*/
JobScheduleHelper.getInstance().start();
logger.info(">>>>>>>>> init xxl-job admin success.");
}
3.2. 客户端启动
这里我们看XxlJobSpringExecutor,实现了 SmartInitializingSingleton 接口,实现该接口的当spring容器初始完成,调用afterSingletonsInstantiated()方法。紧接着执行监听器发送监听后,就会遍历所有的Bean然后初始化所有单例非懒加载的bean。实现DisposableBean当实例bean摧毁时调用destroy()方法。
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);
// start
@Override
public void afterSingletonsInstantiated() {
// init JobHandler Repository
/*initJobHandlerRepository(applicationContext);*/
// init JobHandler Repository (for method) 初始化调度器资源管理器
/**
* ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
* handle名; Handler->MethodJobHandler(反射 Object、Bean、initMethod、destroyMethod)
*/
initJobHandlerMethodRepository(applicationContext);
// refresh GlueFactory
GlueFactory.refreshInstance(1);
// super start 启动
try {
super.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
再看super.start()
public void start() throws Exception {
// init logpath 初始化日志目录,用来存储调度日志执行指令到磁盘
XxlJobFileAppender.initLogPath(logPath);
// init invoker, admin-client 初始化admin链接路径存储集合
// 在AdminBizClient设置好addressUrl+accessToken
initAdminBizList(adminAddresses, accessToken);
// init JobLogFileCleanThread 清除过期日志(30天)
// 根据存储路径目录的日志(目录名为时间),根据其目录时间进行删除,1天跑一次,守护线程
JobLogFileCleanThread.getInstance().start(logRetentionDays);
// init TriggerCallbackThread 回调调度中心任务执行状态
TriggerCallbackThread.getInstance().start();
// init executor-server 执行内嵌服务
/**
* 1. 使用netty开放端口,等待服务端调用
* 2. 维护心跳时间到服务端(心跳30S)
* 3. 向服务端申请剔除服务
*/
initEmbedServer(address, ip, port, appname, accessToken);
}
4. 服务注册
1. 任务执行器
com.xxl.job.core.thread.ExecutorRegistryThread#start
public void start(final String appname, final String address){
...
registryThread = new Thread(new Runnable() {
@Override
public void run() {
// registry
while (!toStop) {
try {
RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
// 遍历所有的调度中心
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
try {
ReturnT<String> registryResult = adminBiz.registry(registryParam);
if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
registryResult = ReturnT.SUCCESS;
logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});