任务调度

摘要:在分布式应用中,集成分布式定时器。比如 Quartz、Elastic-Job、XXL-JOB 等。


目录

[TOC]

任务调度中心

参考:Spring Boot 定时任务入门

定时任务

存在一类需求,是需要去定时执行的,此时就需要使用到定时任务

例如说,

  • 每分钟扫描超时支付的订单,
  • 每小时清理一次日志文件,
  • 每天统计前一天的数据并生成报表,
  • 每个月月初的工资单的推送,
  • 每年一次的生日提醒等等。

JDK

在 JDK 中,内置了两个类,可以实现定时任务的功能:

  • java.util.Timer :可以通过创建 java.util.TimerTask 调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行的时间,也只能排队等待。因为 Timer 是串行的,同时存在 坑坑 ,所以后来 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
  • java.util.concurrent.ScheduledExecutorService :在 JDK 1.5 新增,基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中并发执行,互不影响。这样 就解决了 Timer 串行的问题。

在日常开发中,很少直接使用 Timer 或 ScheduledExecutorService 来实现定时任务的需求。主要有几点原因:

  • 它们仅支持按照指定频率,不直接支持指定时间的定时调度,需要我们结合 Calendar 自行计算,才能实现复杂时间的调度。例如说,每天、每周五、2019-11-11 等等。
  • 它们是进程级别,而我们为了实现定时任务的高可用,需要部署多个进程。此时需要等多考虑,多个进程下,同一个任务在相同时刻,不能重复执行。
  • 项目可能存在定时任务较多,需要统一的管理,此时不得不进行二次封装

调度任务中间件

所以,一般情况下,会选择专业的调度任务中间件

在 Spring 体系中,内置了两种定时任务的解决方案:

  • 第一种,Spring FrameworkSpring Task 模块,提供了轻量级的定时任务的实现。
  • 第二种,Spring Boot 2.0 版本,整合了 Quartz 作业调度框架,提供了功能强大的定时任务的实现。Spring Framework 已经内置了 Quartz 的整合,2.X 版本提供了支持 Quartz 的自动化配置。

在 Java 生态中,还有非常多优秀的开源的调度任务中间件:

在分布式应用中,集成分布式定时器。比如 Quartz、Elastic-Job、XXL-JOB 等。

  • Quartz:配合数据库表也是支持分布式定时任务的。
  • Elastic-job:当当网(基于quartz 二次开发的)弹性分布式任务调度系统。功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片。由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成,使用Elastic-Job可以快速实现分布式任务调度。
  • XXL-JOB:是一个分布式任务调度平台,核心设计目标是开发迅速、学习简单、轻量级、易扩展。
    • 将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
    • 将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
    • 因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性。

对比选型

可能胖友希望了解下不同调度中间件的对比。表格如下:

特 性 quartz elastic-job-lite xxl-job LTS
依赖 MySQL、jdk jdk、zookeeper mysql、jdk jdk、zookeeper、maven
高 可用 多节点部署,通过竞争数据库锁来保证只有一个节点执行任务 通过zookeeper的注册与发现,可以动态的添加服务器 基于竞争数据库锁保证只有一个节点执行任务,支持水平扩容。可以手动增加定时任务,启动和暂停任务,有监控 集群部署,可以动态的添加服务器。可以手动增加定时任务,启动和暂停任务。有监控
任务 分片 ×
管理界面 ×
难易程度 简单 简单 简单 略复杂
高级功能 - 弹性扩容,多种作业模式,失效转移,运行状态收集,多线程处理数据,幂等性,容错处理,spring命名空间支持 弹性扩容,分片广播,故障转移,Rolling实时日志,GLUE(支持在线编辑代码,免发布),任务进度监控,任务依赖,数据加密,邮件报警,运行报表,国际化 支持spring,spring boot,业务日志记录器,SPI扩展支持,故障转移,节点监控,多样化任务执行结果支持,FailStore容错,动态扩容。
版本更新 半年没更新 2年没更新 最近有更新 1年没更新

中心化、去中心化

一个分布式的调度中间件,会存在两种角色:

  1. 调度器:负责调度任务,下发给执行器。
  2. 执行器:负责接收任务,执行具体任务。

那么,如果从调度系统的角度来看,分布式调度中间件的实现方式可以分成两类:

  • 中心化: 调度中心和执行器分离,调度中心统一调度,通知某个执行器处理任务。
    1. XXL-Job
    2. 链家的 kob
    3. 美团的 Crane(暂未开源)
  • 去中心化:调度中心和执行器一体化,自己调度自己执行处理任务。
    1. Elastic Job
    2. 唯品会的 Saturn
    3. Quartz 基于数据库的集群方案
    4. 淘宝的 TBSchedule(暂停更新,只能使用阿里云 SchedulerX 服务)

如果胖友想要更加的理解,可以看看 《中心化 V.S 去中心化调度设计》

任务竞争 V.S 任务预分配

那么,如果从任务分配的角度来看,可以分成两类:

  • 任务竞争:调度器会通过竞争任务,下发任务给执行器。
    1. 链家的 kob
    2. 美团的 Crane(暂未开源)
    3. Quartz 基于数据库的集群方案
  • 任务预分配:调度器预先分配任务给不同的执行器,无需进行竞争。
    1. Elastic Job
    2. 唯品会的 Saturn
    3. 淘宝的 TBSchedule(暂停更新,只能使用阿里云 SchedulerX 服务)

一般来说,基于任务预分配的任务调度平台,都会选择使用 Zookeeper 来协调分配任务到不同的节点上。同时,任务调度平台必须是去中心化的方案,每个节点即是调度器又是执行器。这样,任务在预分配在每个节点之后,后续就自己调度给自己执行。

相比较而言,随着节点越来越多,基于任务竞争的方案会因为任务竞争,导致存在性能下滑的问题。而基于任务预分配的方案,则不会存在这个问题。并且,基于任务预分配的方案,性能会优于基于任务竞争的方案。

这里在推荐一篇 Elastic Job 开发者张亮的文章 《详解当当网的分布式作业框架 elastic-job》 ,灰常给力!

Quartz 是个优秀的调度内核

绝大多数情况下,我们不会直接使用 Quartz 作为我们的调度中间件的选择。但是,基本所有的分布式调度中间件,都将 Quartz 作为调度内核,因为 Quartz 在单纯任务调度本身提供了很强的功能。

不过呢,随着一个分布式调度中间件的逐步完善,又会逐步考虑抛弃 Quartz 作为调度内核,转而自研。例如说 XXL-JOB 在 2.1.0 RELEASE 的版本中,就已经更换成自研的调度模块。其替换的理由如下:

XXL-JOB 最终选择自研调度组件(早期调度组件基于 Quartz);

  • 一方面,是为了精简系统降低冗余依赖。
  • 另一方面,是为了提供系统的可控度与稳定性。

Spring Task

实际场景下,很少使用

  • 创建 #execute() 方法,实现打印日志。同时,在该方法上,添加 @Scheduled 注解,设置每 2 秒执行该方法。

@Scheduled

设置定时任务的执行计划。

常用属性如下:

  • cron 属性:Spring Cron 表达式。例如说,"0 0 12 * * ?" 表示每天中午执行一次,"11 11 11 11 11 ?" 表示 11 月 11 号 11 点 11 分 11 秒执行一次(哈哈哈)。更多示例和讲解,可以看看 《Spring Cron 表达式》 文章。注意,以调用完成时刻为开始计时时间。
  • fixedDelay 属性:固定执行间隔,单位:毫秒。注意,以调用完成时刻为开始计时时间。
  • fixedRate 属性:固定执行间隔,单位:毫秒。注意,以调用开始时刻为开始计时时间。

这三个属性,有点雷同,可以看看 《@Scheduled 定时任务的fixedRate、fixedDelay、cron 的区别》 ,一定要分清楚差异。

不常用属性如下:

  • initialDelay 属性:初始化的定时任务执行延迟,单位:毫秒。
  • zone 属性:解析 Spring Cron 表达式的所属的时区。默认情况下,使用服务器的本地时区。
  • initialDelayString 属性:initialDelay 的字符串形式。
  • fixedDelayString 属性:fixedDelay 的字符串形式。
  • fixedRateString 属性:fixedRate 的字符串形式。

应用配置文件

Quartz

Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。

  • Quartz 自带了集群方案。它通过将作业信息存储到关系数据库中,并使用关系数据库的行锁来实现执行作业的竞争,从而保证多个进程下,同一个任务在相同时刻,不能重复执行。
  • 很多特征,如:数据库支持,集群,插件,EJB 作业预构建,JavaMail 及其它,支持 cron-like 表达式等等。

在 Quartz 体系结构中,有三个组件非常重要:

  • Scheduler :调度器
  • Trigger :触发器
  • Job :任务

Quartz 集群

实际场景下,我们必然需要考虑定时任务的高可用,所以基本上,肯定使用 Quartz 的集群方案。因此本小节,我们使用 Quartz 的 JDBC 存储器 JobStoreTX ,并是使用 MySQL 作为数据库。

定时任务配置

完成上述的工作之后,需要配置 Quartz 的定时任务。目前,有两种方式:

XXL-JOB

因为Quartz 只提供了任务调度的功能,不提供管理任务的管理与监控控制台,需要自己去做二次封装。

XXL-JOB 是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。

依赖、配置文件

application.yml 中,添加 Quartz 的配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
  port: 9090 #指定一个端口,避免和 XXL-JOB 调度中心的端口冲突。仅仅测试之用

# xxl-job
xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
    executor:
      appname: lab-28-executor # 执行器 AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
      ip: # 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
      port: 6666 # ### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
      logpath: /Users/yunai/logs/xxl-job/lab-28-executor # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
      logretentiondays: 30 # 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
    accessToken: yudaoyuanma # 执行器通讯TOKEN [选填]:非空时启用;

XxlJobConfiguration

  • #xxlJobExecutor() 方法,创建了 Spring 容器下的 XXL-JOB 执行器 Bean 对象。
  • 要注意,方法上添加了的 @Bean 注解,配置了启动和销毁方法。

DemoJob

  • 继承 XXL-JOB IJobHandler 抽象类,通过实现 #execute(String param) 方法,从而实现定时任务的逻辑。
  • 在方法上,添加 @JobHandler 注解,设置 JobHandler 的名字。后续,在调度中心的控制台中,新增任务时,需要使用到这个名字。

#execute(String param) 方法的返回结果,为 ReturnT 类型。

  • 当返回值符合 “ReturnT.code == ReturnT.SUCCESS_CODE” 时表示任务执行成功,
  • 否则表示任务执行失败,而且可以通过 “ReturnT.msg” 回调错误信息给调度中心;
  • 从而,在任务逻辑中可以方便的控制任务执行结果。

#execute(String param) 方法的方法参数,为调度中心的控制台中,新增任务时,配置的“任务参数”。一般情况下,不会使用到。

新增执行器

浏览器打开 http://127.0.0.1:8080/xxl-job-admin/jobgroup 地址,即「执行器管理」菜单。

新增执行器

新建任务

浏览器打开 http://127.0.0.1:8080/xxl-job-admin/jobinfo 地址,即「任务管理」菜单。

新增

Elastic-Job

Elastic-Job 是一个分布式调度解决方案,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。

Elastic-Job-Lite 定位为轻量级无中心化解决方案,使用 jar 包的形式提供分布式任务的协调服务。

Elastic-Job 基本是国内开源最好的调度任务中间件的几个中间件,可能没有之一

0%