一、 为什么要定时任务?

​ 常见的业务场景:

​ 电商平台,用户下单半小时未支付自动取消订单。

​ 媒体平台,每十分钟动态抓取其他网站数据为自己所用。

​ 健康平台,每天定时获取个人健康数据。

二、单机定时任务技术选型

1 Timer

1.1 什么是Timer?

Timer内部使用一个叫做TaskQueue的类存放定时任务,它基于最小堆实现的优先级队列。TaskQueue会按照任务距离下一次执行时间的大小将任务排序,保证在堆顶的任务最先执行。这样在执行任务时,每次只需要取出堆顶的任务即可。

    /**
     * TimeTest
     */
    public static void timerTest() {
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("当前时间:" + new Date() + "n" +
                        "TimeTask线程名称1:" + Thread.currentThread().getName());
            }
        };
        System.out.println("当前时间:" + new Date() + "n" +
                "TimeTask线程名称2:" + Thread.currentThread().getName());

        // 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,
        // 其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题
        Timer timer = new Timer("Timer");
        long delay = 1000L;
        timer.schedule(task1, delay);
    }

// 运行结果
当前时间:Thu Nov 18 10:12:27 CST 2021nTimeTask线程名称2:main
当前时间:Thu Nov 18 10:12:28 CST 2021nTimeTask线程名称1Timer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.2 有什么有点和缺陷?
  • 一个Timer一个线程会导致Timer任务执行只能串行执行,一个任务执行时间过长会严重影响其他任务的执行。
  • 发生异常时任务直接停止,且只捕获InterruptedException。
  • 无法使用Corn表达式指定任务具体时间。

2 ScheduledExecutorService

image-20211118205804791

2.1 什么是ScheduledExecutorService?

ScheduledExecutorService是一个接口,有多个实现类,比较常用的就是ScheduledThreadPoolExecutorScheduledThreadPoolExecutor本身是一个线程池,支持任务并发执行,其内部是使用DelayQueue作为任务队列。

    /**
     * scheduledExecutorServiceTest
     */
    public static void scheduledExecutorServiceTest() {
        TimerTask task = new TimerTask() {
            /**
             * The action to be performed by this timer task.
             */
            @Override
            public void run() {
                System.out.println("当前时间:" + new Date() + "n" +
                        "scheduledExecutorServiceTest线程名称1:" + Thread.currentThread().getName());
            }
        };
        System.out.println("当前时间:" + new Date() + "n" +
                "scheduledExecutorServiceTest线程名称2:" + Thread.currentThread().getName());

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
        long delay = 1000L;
        long period = 1000L;
        executor.scheduleAtFixedRate(task, delay, period, TimeUnit.MICROSECONDS);
        try {
            Thread.sleep(delay + period * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executor.shutdown();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2.2 有什么有点和缺陷?

无法使用Corn表达式指定任务具体时间

3 Spring Task

直接使用@Scheduled注解即可定义定时任务。详情请参看《Spring Schedule 实现定时任务》

4 时间轮

时间轮就是一个环形队列(底层一般使用数组实现),队列中的每一个元素都可以存放一个定时任务列表。时间轮中每个时间格代表时间的基本时间跨度,加入时间一秒走一个时间格的话,则时间轮最高精度为1秒。

比如:下面这个12个时间格的时间轮,转完一圈需要12秒,当我们想创建一个3秒后执行的任务时,则只需将定时任务放置在下标为3的时间格中。

20210607171334861

当我们想创建15秒后执行的任务时,就会产生(圈数/轮数)的概念,则需要将该任务放置在第二圈的下标为5的时间格中。

20210607193042151

​ 如上图该时间轮中,第一层(秒针)时间精度为1秒,一圈为20秒。第二层(分针)时间精度为20秒,转一圈为20X20=400秒。第三层(时针)时间精度为400秒,转一圈为400X20=8000秒。如果我们需要350秒后执行的任务,则该任务会被放置在第二层。(350/20=17.5,当秒针转17圈后,分针指针指向17,此时将第17格子的任务移动至第一层,秒针再走10秒(10格),共计350秒。将任务放置到第一层的第10个格子。

三、分布式定时任务技术选型

1 Quartz

2021052814502425

一个很火的开源任务调度框架,完全由Java写成。Quartz 可以说是 Java 定时任务领域的老大哥或者说参考标准,其他的任务调度框架基本都是基于 Quartz 开发的,比如当当网的elastic-job就是基于quartz二次开发之后的分布式调度解决方案。

使用 Quartz 可以很方便地与 Spring 集成,并且支持动态添加任务和集群。但是,Quartz 使用起来也比较麻烦,API 繁琐。

并且,Quzrtz 并没有内置 UI 管理控制台,不过你可以使用 quartzui (opens new window)open in new window 这个开源项目来解决这个问题。

另外,Quartz 虽然也支持分布式任务。但是,它是在数据库层面,通过数据库的锁机制做的,有非常多的弊端比如系统侵入性严重、节点负载不均衡。有点伪分布式的味道。

优缺点总结:

  • 优点: 可以与 Spring 集成,并且支持动态添加任务和集群。
  • 缺点 :分布式支持不友好,没有内置 UI 管理控制台、使用麻烦(相比于其他同类型框架来说)

2 Elastic-Job

20210528144508114

20210608080437356

优缺点总结:

  • 优点 :可以与 Spring 集成、支持分布式、支持集群、性能不错
  • 缺点 :依赖了额外的中间件比如 Zookeeper(复杂度增加,可靠性降低、维护成本变高)

3 XXL-Job

20210528144611728

up-b8ecc6acf651f112c4dfae98243d72adea3

从上图可以看出,XXL-JOB调度中心执行器 两大部分组成。调度中心主要负责任务管理、执行器管理以及日志管理。执行器主要是接收调度信号并处理。另外,调度中心进行任务调度时,是通过自研 RPC 来实现的。

不同于 Elastic-Job 的去中心化设计, XXL-JOB 的这种设计也被称为中心化设计(调度中心调度多个执行器执行任务)。

Quzrtz 类似 XXL-JOB 也是基于数据库锁调度任务,存在性能瓶颈。不过,一般在任务量不是特别大的情况下,没有什么影响的,可以满足绝大部分公司的要求。

4 Power-Job

20210528145009701

up-795f5e9b0d875063717b1ee6a08f2ff1c01