一、使用XXL-JOB的原因
1、 Quartz的不足
Quartz作为开源任务调度中的佼佼者,是任务调度的首选。但是在集群环境中,Quartz采用API的方式对任务进行管理,这样存在以下问题:
- 通过调用API的方式操作任务,不人性化。
- 需要持久化业务的QuartzJobBean到底层数据表中,系统侵入性相当严重。
- 调度逻辑和QuartzJobBean耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务。
XXL-JOB弥补了Quartz的上述不足之处。
2. RemoteHttpJobBean
常规的Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。
XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自的调度参数维护在各自的扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的调度参数发起远程调用,调用各自的远程执行器服务。
这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
3. 架构设计
XXL-JOB将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”只负责发起调度请求。
将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中的业务逻辑。
因此,“调度”和“任务”两部分可以解耦成调度模块和执行模块,提高业务系统的整体稳定性和扩展性:
调度模块(调度中心):负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统的性能不再受限于任务模块;支持可视化、简单且动态的管理调度信息,包括任务的新建、更新、删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。
执行模块(执行器):负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;接收“调度中心”的执行请求、终止请求和日志请求等。
XXL-JOB的系统架构,如下图所示:
三、建立SpringBoot示例工程
通过STS建立SpringBoot的示例工程,名称为xxl-job-demo
。本文采用的配置如下:
- SpringBoot版本为2.0.1
- 项目依赖关系选择Web
这个示例工程将作为“执行器”,接收“调度中心”的调度请求,然后将Redis中的消息输出至任务日志。具体的创建步骤,本文不再赘述。
这个示例工程会用到Jedis客户端,其配置文件会交给Disconf托管,请参考《基于注解的分布式配置文件和配置项》,本文不再赘述。
四、整合XXL-JOB执行器
1. Maven依赖
打开pom.xml
文件,添加XXL-JOB执行器的依赖关系,如下所示:
com.xuxueli
xxl-job-core
1.9.1
2. 执行器配置文件
打开application.properties
文件,添加执行器配置。application.properties
文件的全部内容,如下所示:
XXL-JOB执行器的相关配置项的意义,如下所示:
xxl.job.admin.addresses
调度中心的部署地址。若调度中心采用集群部署,存在多个地址,则用逗号分隔。执行器将会使用该地址进行”执行器心跳注册”和”任务结果回调”。xxl.job.executor.appname
执行器的应用名称,它是执行器心跳注册的分组依据。xxl.job.executor.ip
执行器的IP地址,用于”调度中心请求并触发任务”和”执行器注册”。执行器IP默认为空,表示自动获取IP。多网卡时可手动设置指定IP,手动设置IP时将会绑定Host。xxl.job.executor.port
执行器的端口号,默认值为9999。单机部署多个执行器时,注意要配置不同的执行器端口。xxl.job.accessToken
执行器的通信令牌,非空时启用。xxl.job.executor.logpath
执行器输出的日志文件的存储路径,需要拥有该路径的读写权限。xxl.job.executor.logretentiondays
执行器日志文件的定期清理功能,指定日志保存天数,日志文件过期自动删除。限制至少保存3天,否则功能不生效。
注意,XXL-JOB执行器的配置文件也可以交给Disconf进行托管。
3. 执行器配置类
还需要新建一个执行器配置类,用来读取执行器的配置信息。新建一个名为com.example.demo.config
的包,然后在这个包中新建一个名为XxlJobConfig
的类,主要内容如下所示:
XxlJobConfig
配置类有两点需要注意:
组件扫描
第2行使用@ComponentScan
注解,扫描com.example.demo.jobhandler
包,将其中的任务处理器加载至Spring容器。获取执行器实例
第29行的xxlJobExecutor()
方法会实例化一个XXL-JOB执行器对象,执行器初始化时调用它的start()
方法,执行器销毁时调用它的destroy()
方法。
五、执行器
在浏览器中访问http://10.15.1.21:8081
,登录XXL-JOB调度中心,默认的用户名/密码是admin/123456。
点击进入“执行器管理”页面,然后点击“新增执行器”按钮,弹出新增执行器窗口,如下图所示:
新增执行器时,需要填写的信息,如下所示:
AppName:这是用来唯一标识每个执行器集群的应用名称,执行器会周期性地以AppName为参数进行自动注册。可通过该配置自动发现注册成功的执行器,供任务调度时使用。
名称:执行器的名称,因为AppName限制字母数字等组成,可读性不强,名称可以提高执行器的可读性。
排序:执行器的排序,系统中需要执行器的地方,如任务新增,将会按照该排序读取可用的执行器列表。
-
注册方式:调度中心获取执行器地址的方式,有以下两种:
- 自动注册:执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址。
- 手动录入:人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用。
机器地址:只有在“注册方式”为“手动录入”时可编辑,支持人工维护执行器的地址信息。
注意,AppName的取值应该和示例工程的application.properties
文件中的xxl.job.executor.appname
字段的取值相同,注册方式应该选择自动注册
。新增完成之后,就可以在执行器列表中看到新建的执行器,如下图所示:
注意,执行器列表的“OnLine 机器地址”字段会在执行器启动时,显示执行器的IP地址和端口号。
六、任务
1. 任务调度属性
在XXL-JOB调度中心,点击进入“任务管理”页面,然后点击“新增任务”按钮,弹出新增任务窗口,如下图所示:
新增任务时,需要填写的信息,如下所示:
执行器:任务绑定的执行器,任务触发调度时将会自动发现注册成功的执行器,实现任务自动发现功能;另一方面,也可以方便地进行任务分组。每个任务必须绑定一个执行器,可以在“执行器管理”页面进行设置。
任务描述:任务的描述信息,便于任务管理。
-
路由策略:当执行器集群部署时,提供丰富的路由策略,包括:
- FIRST(第一个):固定选择第一个机器。
- LAST(最后一个):固定选择最后一个机器。
- ROUND(轮询):轮流选择每台机器。
- RANDOM(随机):随机选择在线的机器。
- CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
- LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举。
- LEAST_RECENTLY_USED(最近最久未使用):最久为使用的机器优先被选举。
- FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度。
- BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度。
- SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务。
Cron:触发任务执行的Cron表达式,请参考Cron的维基页面。
-
运行模式
- BEAN模式:任务以JobHandler的方式维护在执行器端;需要结合 “JobHandler”属性匹配执行器中的任务;
- GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并以“groovy”源码的方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务;
- GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段“shell”脚本;
- GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段“python”脚本;
- GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段“nodejs”脚本;
JobHandler:只有在运行模式为“BEAN模式”时生效,对应执行器中新开发的JobHandler类的“@JobHandler”注解自定义的value值。
子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
-
阻塞处理策略:调度过于密集,执行器来不及处理时的处理策略:
- 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行。
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败。
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务。
-
失败处理策略:调度失败时的处理策略:
- 失败告警(默认):调度失败和执行失败时,都将会触发失败报警,默认会发送报警邮件。
- 失败重试:调度失败时,除了进行失败告警之外,将会自动重试一次;注意在执行失败时不会重试,而是根据回调返回值判断是否重试。
任务参数:任务执行所需的参数,多个参数时用逗号分隔,任务执行时将会把多个参数转换成数组传入。
报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔。
负责人:任务的负责人。
注意,编辑任务时也会弹出类似的窗口,其中的输入项请参考新增任务窗口。
接下来,本文将详述BEAN模式任务和GLUE(Java)模式任务,以及分片广播路由策略,这些是XXL-JOB最常用的功能。
2. BEAN模式
任务逻辑以JobHandler的形式存在于“执行器”所在项目中,开发流程如下:
Step-1 开发JobHandler代码
在示例工程中,新建com.example.demo.jobhandler
包,用来存储任务的业务逻辑代码。在这个包中新建DemoJobHandler
任务类,关键代码如下所示:
上述代码有三点需要注意:
- 必须使用XXL-JOB的
@JobHandler
注解(第1行),指定JobHandler的名称为“demoJobHandler”,在调度中心新建任务的JobHandler字段的取值要与此相同。 - 必须继承
IJobHandler
抽象类(第3行),并且实现它的execute()
方法,这是实现任务逻辑的方法。 -
IJobHandler
抽象类还有init()
方法和destroy()
方法,这两个方法是空方法,在任务实例初始化和销毁时调用,任务实现类可以选择性地覆盖这两个方法。
Step-2 新建调度任务
参考上文“任务调度属性”对新建的任务进行参数配置,运行模式选择“BEAN模式”,JobHandler属性填写任务注解@JobHandler
中定义的值,如下图所示:
调度中心会每隔15分钟调度一次demoJobHandler任务。
3. GLUE(Java)模式
任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。开发流程如下:
Step-1 新建调度任务
参考上文“任务调度属性”对新建的任务进行参数配置,运行模式选择“GLUE模式(Java)”,如下图所示:
调度中心会每隔15分钟调度一次这个任务。
Step-2 开发任务代码
在任务列表中选中指定的GLUE(Java)任务,点击该任务右侧的“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
版本回溯功能:在GLUE任务的Web IDE界面,选择右上角下拉框“版本回溯”,会列出该GLUE任务的更新历史(支持30个版本的版本回溯),选择相应版本即可显示该版本代码,保存后GLUE代码即回退到对应的历史版本。GLUE任务代码和Web IDE界面,如下图所示:
4. 分片广播任务
执行器集群部署时,任务路由策略选择“分片广播”的情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时传递分片参数,可以根据分片参数开发分片任务。
“分片广播”以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
“分片广播”和普通任务开发流程一致,不同之处在于可以获取分片参数,通过分片参数进行分片业务处理。开发流程如下:
Step-1 开发JobHandler代码
在示例工程的com.example.demo.jobhandler
包中,新建ShardingJobHandler
任务类,关键代码如下所示:
上述代码的第9行获取分片参数,第10行获取分片参数的两个属性:
shardingVO.getIndex()
当前分片序号(从0开始),执行器集群列表中当前执行器的序号。shardingVO.getTotal()
总分片数,执行器集群的总机器数量。
Step-2 新建调度任务
参考上文“任务调度属性”对新建的任务进行参数配置,运行模式选择“BEAN模式”,路由策略选择“分片广播”,JobHandler属性填写任务注解@JobHandler
中定义的值,如下图所示:
调度中心会每隔15分钟广播调度一次shardingJobHandler任务。
分片广播的路由策略不仅适用于BEAN运行模式,而且也适用于GLUE(Java)运行模式。这项功能适用于以下业务场景:
- 分片任务场景
10个执行器的集群来处理10w条数据,每台机器只需要处理1w条数据,耗时降低10倍。 - 广播任务场景
广播执行器机器运行shell脚本、广播集群节点进行缓存更新等。
5. 任务列表
在XXL-JOB调度中心,点击进入“任务管理”页面,可以看到指定执行器的任务列表,如下图所示:
在任务列表中,可以看到每个任务的任务ID、任务描述、运行模式、Cron、负责人和状态等信息。用户可以对任务进行以下几种操作:
- 执行:手动触发一次任务调度,不影响原有调度规则。
- 暂停/恢复:可对任务进行“暂停”和“恢复”操作。需要注意的是,此处的暂停/恢复仅针对任务的后续调度触发行为,不会影响到已经触发的调度任务。
- 日志:可以查看任务历史调度日志。在历史调入日志界面可查看每次任务调度的调度结果、执行结果等,点击“执行日志”按钮可查看执行器完整日志。
- 编辑:在弹出的“编辑任务”界面更新任务属性后保存即可,可以修改设置的任务属性信息。
- GLUE:该操作仅针对GLUE任务。将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发。
- 删除:删除这个任务。
七、任务调度日志
在XXL-JOB调度中心,点击进入“调度日志”页面。
1. 查看调度日志
在“调度日志”页面可以查看每次任务调度的调度结果、执行结果等信息,如下图所示:
从调度日志可以获取以下信息:
- 调度时间:“调度中心”触发本次调度并向“执行器”发送任务执行信号的时间。
- 调度结果:“调度中心”触发本次调度的结果,200表示成功,500或其他表示失败。
- 调度备注:“调度中心”触发本次调度的日志信息。
- 执行时间:“执行器”中本次任务执行结束后回调的时间。
- 执行结果:“执行器”中本次任务执行的结果,200表示成功,500或其他表示失败。
- 执行备注:“执行器”中本次任务执行的日志信息。
在示例工程中,调度日志位于/data/applogs/xxl-job/xxl-job-demo.log
,可以在logback.xml
文件中进行配置。
2. 查看执行日志
点击某行日志右侧的 “执行日志” 按钮,可跳转至执行日志界面,可以查看业务代码中打印的完整日志,如下图:
在示例工程中,执行日志位于/data/applogs/xxl-job/jobhandler
目录中,可以在logback.xml
文件中进行配置。
3. 终止运行中的任务
这项功能只针对执行中的任务。在任务日志页面,点击右侧的“终止任务”按钮,将会向本次任务对应的执行器发送任务终止请求,将会终止掉本次任务,同时会清空掉整个任务执行队列,如下图所示:
任务终止是通过“interrupt”执行线程的方式实现的,将会触发“InterruptedException”异常。因此,如果JobHandler内部捕获到该异常并消化掉的话,任务终止功能将不起作用。
因此, 如果遇到上述任务终止不起作用的情况, 需要在JobHandler中针对“InterruptedException”异常进行特殊处理(向上抛出)。另外,在JobHandler中开启子线程时,子线程也不可捕获处理“InterruptedException”,应该主动向上抛出。
4. 删除执行日志
在任务日志页面,选择执行器和任务之后,点击右侧的“清理”按钮将会出现“日志清理”弹框,弹框中支持选择不同类型的日志清理策略,选中后点击“确定”按钮即可进行日志清理操作,如下图所示:
详情介绍:
更新主题详情
420天以来,Java架构更新了 888个主题,已经有156+位同学加入。微信扫码关注java架构,获取Java面试题和架构师相关题目和视频。上述相关面试题答案,尽在Java架构中。
最后:特别感谢以下博客
1、分布式任务调度平台XXL-JOB搭建教程
2、XXL-JOB的基本用法