说完了 xxl-job 的执行器原理,再来聊聊调度中心是如何调度任务的

前言

在上一篇 xxl-job 执行器原理分析 一文中,我们提到了 xxl-job 框架中包含了两个核心模块:调度中心执行器, 其中调度中心主要负责 任务的调度 , 而执行器负责 任务的执行, 两者各司其职。 紧接着我们通过画图的方式对 执行器 的内部构造进行了分析,并且还对 Job 的执行流程进行了梳理。

本文我们继续围绕任务的调度流程对 调度中心 进行剖析, 内容依然参照 xx-job v2.x 版本的源码。

正文

再看一遍 xxl-job 架构图:

在这里插入图片描述

调度中心主要提供了两个功能: 系统管理 和 任务调度。其余的都是一些辅助功能。

  • 系统管理正如图中所示的那样, 包括任务管理、执行器管理、日志管理。还提供了管理界面。
  • 任务调度就是负责从数据中心拉取任务,并按照执行时间将任务投递给执行器。

一、调度器的组成结构

在这里插入图片描述

1.1 两个核心线程

当调度中心启动后,会启动以下两个线程:

  1. schedulerThread

scheudlerThread主要做如下两件事情:

  • 从数据中心(db),也就是 xxl_job_info表中扫描出符合 条件 1 的任务, 条件1 限制如下:
    • 任务执行时间 小于(当前时间 + 5 s)
    • 限制扫描个数, 这个值是动态的,具体值跟后面的提到的 快慢线程池 中线程数量有关系。
    	 count = treadpool-size * trigger-qps  (each trigger cost 50ms, qps = 1000/50 = 20) 
    
    treadpool-size = (getTriggerPoolFastMax() + getTriggerPoolSlowMax()) * 20
    // 看完后面的快慢线程池, 再来理解这里会更容易
    
  • 扫描出来的任务被划分为以下 3 类: 在这里插入图片描述
  1. ringThread

ringThread的作用就是不断从 容器 中读取 当前时间点需要执行 的任务, 读取出来的任务会交给一个叫 快慢线程池 的东西去将任务传递给调度器去执行。

1.2 时间轮

上述的 ringThread和 容器 共同组成了一个时间轮。

关于时间轮,如果展开来讲内容会很多,需要详细了解的可以参考 这篇文章,或者自行搜索, 本文只做简单了解。

简单来讲,时间轮实现了 延迟执行 的功能,它在 xxl-job 中的作用就是让 还未到达执行时间 的任务,按照预计的时间通过 快慢线程池 一个一个送到 执行器 中去执行。

  • 时间轮的数据结构一般是 数组 + 链表, 和 jdk1.7 中的 HashMap 是一个道理,链表中的每个节点就是一个待执行的任务。

  • xxl-job 中的时间轮可以形象描述为以下这张图,像一个只有秒针的钟表一样。

  • ringThread 线程运行过程中,每秒会扫过一个刻度,假设当前刻度位置存在 job 链表,就把链表中的所有 job 取出来,最后丢给 快慢线程池

  • 当然 xxl-job 为了避免处理耗时太长,会跨过刻度,多向前校验一个刻度;也就是当指针指到 2s 时,会把 1s 和 2s 位置的任务同时读取出来。 在这里插入图片描述

1.3 快慢线程池

上面提到任务从数据中心扫描出来后,随之就会被丢到快慢线程池中,快慢线程池的定义如下:

      fastTriggerPool = new ThreadPoolExecutor(
                10,
                XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(1000),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
                    }
                });

        slowTriggerPool = new ThreadPoolExecutor(
                10,
                XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(2000),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
                    }
                });

由上可知, 快慢线程池包含了两个线程池 fast 和 slow,当一个 job 提交到快慢线程池后,快慢线程池会根据一些条件, 选择其中一个线程池去执行后续的操作。

快慢线程池的作用如下:

实现线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入”Slow”线程池,避免耗尽调度线程,提高系统稳定性;

什么是慢任务?

如果一个任务在 1 分钟内,它的执行超时次数超过 10 次,就被归为 慢任务

当具体的快或者慢线程池接收到调度任务时,会通过 RPC 远程调用去触发 执行器 完成任务的执行逻辑。

当执行器接收到调度任务时,具体是如何执行任务的,可以参考 xxl-job 执行器原理分析 一文。

二、源码入口

com.xxl.job.admin.core.thread.JobScheduleHelper#start

com.xxl.job.admin.core.thread.JobTriggerPoolHelper#addTrigger

com.xxl.job.core.biz.client.ExecutorBizClient#run

总结

本文基于 xxl-job v2.x 的源码分析了 xxl-job 调度器的组成结构 以及 调度中心是如何触发任务的。

调度器主要包含了以下模块:

  • schedulerThread: 负责从数据中心扫描需要执行的任务
  • ringThread: 负责精准地控制预计需要执行的任务
  • 快慢线程池:通过包装两个线程池,去分别执行 快任务 和 慢任务 的调度过程。

这里面的主要难点是引出了一个时间轮的概念,文中并未做详细的描述,另外时间轮的用法在很多地方都有应用, 比如 Netty、Dubbo、Kafka 等。

最后,本文中仍有很多细节没有展开来讲,需要读者自行去挖掘。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章