浅谈XXLJob 分布式任务调度

发布时间 2023-10-31 16:16:46作者: 潇潇love涛涛

一、前言

1、什么是XXLJob?

xxl-job是一个分布式的任务调度平台,他的优点是上手简单,开发迅速,开箱即用,也是个轻量级的任务调度平台。
xxl-job框架主要用于处理分布式的定时任务,其主要由调度中心和执行器组成。

  • 调度模块(调度中心):
    负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;
    支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。
  • 执行模块(执行器):
    负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;
    接收“调度中心”的执行请求、终止请求和日志请求等。

2、XXLJob的原理

执行器的注册与发现

执行器的注册发现主要是关系两张表:
xxl_job_registry:执行器的实例表,保存实例信息和心跳信息。
xxl_job_group:每个服务注册的实例表。
执行器启动线程每隔30秒向注册表xxl_job_registry请求一次,更新执行器的心跳信息,调度中心启动线程每隔30秒检测一次xxl_job_registry,将超过90秒还没有收到心跳的实例信息从xxl_job_registry删除,并更新xxl_job_group服务的实例列表信息。

调度中心调用执行器

调度中心通过循环不停的:
1、关闭自动提交事务
2、利用mysql的悲观锁,其他事务无法进入
select * from xxl_job_lock where lock_name = 'schedule_lock' for update
3、读取数据库中的xxl_job_info:记录定时任务的相关信息,该表中有trigger_next_time字段表示下一次任务的触发时间。拿到距离当前时间5s内的任务列表,分为三种情况处理:

  • 对于当前时间-任务的下一次触发时间>5,直接调过不执行,重置trigger_next_time的时间。(超过5s)
  • 对于任务的下一次触发时间<当前时间<任务的下一次触发时间+5的任务(不超过5s的):
    1. 开线程处理执行触发逻辑,根据当前时间更新下一次任务触发时间
    2. 如果新的任务下一次触发时间-当前时间<5,放到时间轮中,时间轮是一个map:
      private volatile static Map<Integer, List> ringData = new ConcurrentHashMap<>();
    3. 根据新的任务下一次触发时间更新下下一次任务触发时间
  • 对于任务的下一次触发时间>当前时间,将其放入时间轮中,根据任务下一次触发时间更新下下一次任务触发时间

4、commit提交事务,同时释放排他锁

执行器的操作:
1、执行器接收到调度中心的调度信息,将调度信息放到对应的任务的等待队列中
2、执行器的任务处理线程从任务队列中取出调度信息,执行业务逻辑,将结果放入一个公共的等待队列中(每个任务都有一个单独的处理线程和等待队列,任务信息放入该队列中)
3、执行器有一个专门的回调线程定时批量从结果队列中取出任务结果,并且回调告知调度中心

3、XXLJob和quartz的对比

quartz采用api的方式调用任务,不方便,但是xxl-job使用的是管理界面。
quartz比xxl-job代码侵入更强
quartz调度逻辑和QuartzJobBean耦合在一个项目中,当任务增多,逻辑复杂的时候,性能会受到影响
quartz底层以抢占式获取db锁并且由抢占成功的节点运行,导致节点负载悬殊非常大;xxl-job通过执行器实现协同分配式运行任务,各个节点比较均衡。

二、操作

2、如何使用

准备阶段

源码仓库地址:https://github.com/xuxueli/xxl-job
中央仓库地址:

<!-- http://repo1.maven.org/maven2/com/xuxueli/xxl-job-core/ -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${最新稳定版本}</version>
</dependency>

快速入门

解压,idea打开后,发现目录结构是这样的:

1)、文件介绍:
doc :文档资料
xxl-job-admin :调度中心,项目源码
xxl-job-core :公共Jar依赖
xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
2)、初始化数据库,运行doc/db/tables_xxl_job.sql中的sql生成数据库和表,如图(注释是没有的,我根据自己的理解加上去的):

 数据库中表介绍:

  - xxl_job_group:执行器信息表,维护任务执行器信息;
  - xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;

  - xxl_job_lock:任务调度锁表;

  - xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
  - xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;
  - xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
  - xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
  - xxl_job_user:系统用户表;

3)、调度中心配置及部署
调度中心配置文件地址:/xxl-job/xxl-job-admin/src/main/resources/application.properties

### 调度中心JDBC链接
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
### 报警邮箱
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=xxx@qq.com
spring.mail.password=xxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
### 调度中心通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=
### 调度中心国际化配置 [必填]: 默认为 "zh_CN"/中文简体, 可选范围为 "zh_CN"/中文简体, "zh_TC"/中文繁体 and "en"/英文;
xxl.job.i18n=zh_CN
## 调度线程池最大线程配置【必填】
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
### 调度中心日志表数据保存天数 [必填]:过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能;
xxl.job.logretentiondays=30

4)、启动xxl-job-admin

调度中心访问地址:http://localhost:8080/xxl-job-admin ,默认登录账号 “admin/123456”, 登录后运行界面如下图所示。

5)、执行器配置及部署
pom中jar包的引入:

<!-- xxl-job-core -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${project.parent.version}</version>
</dependency>

执行器配置及说明

### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-demo
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30

执行器中的XxlJobConfig将根据配置文件中配置生成XxlJobSpringExecutor:

@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
    logger.info(">>>>>>>>>>> xxl-job config init.");
    XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
    xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
    xxlJobSpringExecutor.setAppname(appname);
    xxlJobSpringExecutor.setIp(ip);
    xxlJobSpringExecutor.setPort(port);
    xxlJobSpringExecutor.setAccessToken(accessToken);
    xxlJobSpringExecutor.setLogPath(logPath);
    xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

    return xxlJobSpringExecutor;
}

6)、定时任务编写**
在项目中集成xxl-job-core 依赖之后,在指定的包下创建一个job类,使用XxlJob注解将该方法标注成定时方法。如图(不要忘了加上@Component):

7)、在调度中心任务管理中添加调度任务**

从上到下解释:
Cron:Cron表达式,来定义任务什么时候执行,表达式不熟悉的这里也有表达式生成器。

运行模式:运行模式主要分为 Bean模式以及方法模式。Bean模式主要是以jobHandler方式维护在执行器端;需要结合JobHandler属性匹配执行器中的任务。
方法模式:为Job方法添加注解 “@XxlJob(value=“自定义jobhandler名称”, init = “JobHandler初始化方法”, destroy = “JobHandler销毁方法”)”,注解value值对应的是调度中心新建任务的JobHandler属性的值

JobHandler:当选的是Bean模式时才会让填,主要是填你的执行方法名。
任务参数:当你的定时方法需要传参时,就可以在这里进行传参。没有可不填。

三、小结

xxl-job给我的感觉就是很小,因为他真的没有什么复杂的东西在里面,学习起来很简单,开发也很简单。
但是xxl-job也很强大,他会将任务的调度执行,和业务代码分开,符合我们开发的解耦,代码入侵很少。
另外,本文参考:https://blog.csdn.net/weixin_44713306/article/details/127751024