简介

用xxl-job做后台任务管理, 主要是快速解决定时任务的HA问题, 项目代码量不大, 功能精简, 没有特殊依赖. 因为产品中用到了这个项目, 上午花了点时间研究了一下运行机制. 把看到的记一下.

环境

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

运行需要 JDK1.8, MySQL5.7

数据库结构

  • 库编码 utf8mb4_unicode_ci
  • Table: xxl_job_group

    任务分组, 组名, 只支持一级分组, address_list 字段支持多个执行端地址, 逗号分隔
  • Table: xxl_job_info

    任务主表, 记录了任务明细, 调度明细以及预警设置
  • Table: xxl_job_log

    任务每次执行的日志
  • Table: xxl_job_log_report

    按日对执行日志进行统计的结果
  • Table: xxl_job_logglue
  • Table: xxl_job_registry

    用于登记任务的执行者, 记录group:分组, key:名称, value:接口地址. 名称是可以重复的, 接口地址会添加到任务分组表中的注册字段
  • Table: xxl_job_user

    简单的登录控制, 与其它表没有关联
  • Table: xxl_job_lock

    单字段表, 用于并发时加锁避免冲突

代码结构

  • 项目用到的都是常见组件, MyBatis, FreeMarker, Bootstrap, 当前版本基于SpringBoot 2.6.7
  • 线上运行的是 xxl-job-admin 模块, 提供执行端注册, 任务发起和日志记录等服务
  • 项目中需要实现 xxl-job-executor, 项目中提供了例子

项目文件结构如下

├───doc
│ ├───db # 初始化的sql
│ └───images
├───xxl-job-admin # 运行的服务端模块, 提供界面和调度
│ └───src
│ ├───main
│ │ ├───java
│ │ │ └───com
│ │ │ └───xxl
│ │ │ └───job
│ │ │ └───admin
│ │ │ ├───controller
│ │ │ │ ├───annotation
│ │ │ │ ├───interceptor
│ │ │ │ └───resolver
│ │ │ ├───core
│ │ │ ├───dao
│ │ │ └───service
│ │ │ └───impl
│ │ └───resources
│ │ ├───i18n # 多国化, 简繁英
│ │ ├───mybatis-mapper # xml形式的mapper
│ │ ├───static # 前端静态文件
│ │ └───templates # Freemarker模板
│ └───test
│ └───java

├───xxl-job-core # 公用jar包, 模块内部依赖
│ └───src
│ └───main
│ └───java

└───xxl-job-executor-samples
├───xxl-job-executor-sample-frameless # 任务执行层示例
│ └───src
│ ├───main
│ │ ├───java
│ │ └───resources
│ └───test
│ └───java
└───xxl-job-executor-sample-springboot # 使用SpringBoot的执行层示例
└───src
├───main
│ ├───java
│ └───resources
└───test

运行机制

执行端需要准备以下信息

  • adminAddresses 服务端地址, 例如 http://127.0.0.1:8080/xxl-job-admin

  • accessToken 貌似是服务端的token, 在调用服务端 api/registry, api/registryRemove 等操作时需要验证

  • appname 执行端名称

  • address 执行端地址, 和 ip:port 二选一, 存在则覆盖 ip:port

  • ip 执行端IP

  • port 执行端服务端口

  • 执行端启动后将自己注册到服务端, 等待回调

  • 任务执行通过 XxlJobTrigger.processTrigger() 发起, 准备参数, 并在分组中选择一个地址

  • 根据这个地址取得 ExecutorBiz, 调用 executorBiz.run() 执行任务

  • 服务端: 通过 ExecutorBizClient,

    • 调用XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerParam, String.class);
    • 其中 accessToken 是服务端的accessToken
  • 执行端: 通过 ExecutorBizImpl.run()

    • 调用 XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());得到XxlJob方法
    • 通过 XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason) 执行

非 Spring 的场景

通过调用 FrameLessXxlJobConfig.getInstance().initXxlJobExecutor() 这个方法将 XxlJobSimpleExecutor 实例化, 并注册到xxl_job服务端

Spring 场景

  • @Configuration 中, 将 XxlJobSpringExecutor 作为一个 @Bean 添加到 Spring context
  • XxlJobSpringExecutor 是 XxlJobExecutor 的子类并实现了 SmartInitializingSingleton 接口的 afterSingletonsInstantiated()方法
  • afterSingletonsInstantiated()方法中
    • 调用 initJobHandlerMethodRepository(), 在这个方法中, 找到所有@XxlJob注解的方法
    • 通过 registJobHandler(), 将@XxlJob方法添加到private static ConcurrentMap<String, IJobHandler> jobHandlerRepository
    • 调用 XxlJobExecutor.start(), 将自己注册到 xxl_job 服务端

远程调用服务

xxl_job 并未使用Spring的服务机制, 而是内部实现了一个侦听指定IP+端口的服务. 这个实现对应的类是 EmbedServer, 服务基于 Netty, 核心代码是

// start server
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
.addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);

这行代码注册了内部的XxlJob方法

.addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool)

处理远程请求时, 在下面的代码中, 通过executorBiz.run(triggerParam)调用XxlJob方法

private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
//...
// services mapping
try {
switch (uri) {
case "/beat":
return executorBiz.beat();
case "/idleBeat":
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
case "/run":
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
case "/kill":
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
case "/log":
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
default:
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping(" + uri + ") not found.");
}
} catch (Exception e) {
//...
}

锁机制

通过select ... for update实现的, 这个表并没有放到 MyBatis, 在 JobScheduleHelper 中, 通过

preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();

得到锁, 在方法末尾释放

// close PreparedStatement
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}

xxl-job定时调度任务Java代码分析的更多相关文章

  1. java代码分析及分析工具

    一个项目从搭建开始,开发的初期往往思路比较清晰,代码也比较清晰.随着时间的推移,业务越来越复杂.代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰. 作为一个互联网电子商务网站的业务支撑系统,业务复 ...

  2. 贪吃蛇的java代码分析(二)

    代码剖析 贪吃蛇是一款十分经典的小游戏,对初入coding的朋友来说,拿贪吃蛇这样一个案例来练手十分合适,并不高的难度和成功后的成就感都是学习所必须的.下面我将依照我当时的思路,来逐步分析实现的整个过 ...

  3. 贪吃蛇的java代码分析(三)

    代码剖析 在上一篇文章中,我们完成了贪吃蛇部分代码的构造.回头审视我们写的代码与思路,会发现我们遗漏了一个重要的地方,那就是:贪吃蛇的自身移动.想必大家都知道,贪吃蛇自身是会自己移动的,并且会跟随你的 ...

  4. quartz群调查调度机制和源代码分析

    pageId=85056282#quartz集群调度机制调研及源代码分析-quartz2.2.1集群调度机制调研及源代码分析" style="color:rgb(59,115,17 ...

  5. 贪吃蛇的java代码分析(一)

    自我审视 最近自己学习java已经有了一个多月的时间,从一开始对变量常量的概念一无所知,到现在能勉强写几个小程序玩玩,已经有了长足的进步.今天没有去学习,学校里要进行毕业答辩和拍毕业照了,于是请了几天 ...

  6. java 代码分析工具——JDepend

    最近学习Mybatis的官方文档,看到了[项目文档]一节有很多内容没有见过,做个笔记,理解一下. 百科上的介绍,我竟然都看懂了,那就不找其他地方的资料了. JDepend 一个开放源代码的可以用来评价 ...

  7. Java开发分析工具JProfiler的详细使用方法解析

    JProfiler 11 是一款功能强大的Java代码分析工具,JProfiler的直观UI可帮助您解决性能瓶颈,确定内存泄漏并了解线程问题且JProfiler 11 Mac破解版配置会话非常简单,第 ...

  8. java 多线程——quartz 定时调度的例子

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  9. Java学习笔记 -- Java定时调度工具Timer类

    1 关于 (时间宝贵的小姐姐请跳过) 本教程是基于Java定时任务调度工具详解之Timer篇的学习笔记. 什么是定时任务调度 基于给定的时间点,给定的时间间隔或者给定的执行次数自动执行的任务. 在Ja ...

  10. java定时调度器解决方案分类及特性介绍

    什么是定时调度器? 我们知道程序的运行要么是由事件触发的,而这种事件的触发源头往往是用户通过ui交互操作层层传递过来的:但是我们知道还有另外一种由机器系统时间触发的程序运行场景.大家想想是否遇到或者听 ...

随机推荐

  1. java基础-冒泡排序以及稀疏数组

    java基础 以下内容为本人的学习笔记,如需要转载,请声明原文链接   https://www.cnblogs.com/lyh1024/p/16720908.html Ø 冒泡排序原理: 比较数组中, ...

  2. PHP全栈开发(八):CSS Ⅳ 文本格式及字体

    文本系列属性主要是设置文本格式的,例如.... 颜色 body {color:red;} h1 {color:#00ff00;} p.ex {color:rgb(0,0,255); 可以设置文本的居中 ...

  3. mac通过docker一键部署Nexus3

    目录 mac通过docker一键部署Nexus3 一.前言 二.系统配置 三.安装步骤 1.Dockerhub查看镜像地址 2.一键安装 2.1.克隆脚本 2.2.安装程序 2.2.1.程序安装详情 ...

  4. Spring Boot 源码学习之转载

    这次的学习,主要转载了 波波老师的笔记,后续会自己整理一份 1.Spring-Boot源码分析-源码编译:https://dpb-bobokaoya-sm.blog.csdn.net/article/ ...

  5. Java中的名称命名规范

    包名:多单词组成时所有字母都小写:xxxyyyzzz 类名.接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz 变量名.方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首 ...

  6. 在不受支持的 Mac 上安装 macOS Ventura、Monterey、Big Sur (OpenCore Legacy Patcher)

    请访问原文链接:https://sysin.org/blog/install-macos-13-on-unsupported-mac/,查看最新版.原创作品,转载请保留出处. 作者主页:www.sys ...

  7. <四>1:全面掌握Const的用法

    const怎么理解? const修饰的变量不能够在作为左值!!初始化完成后,值不能被修改!! C 和C++中const 的区别? 在C程序中 test.c const int a; 只定义,不做初始化 ...

  8. 精简docker的导出镜像

    Docker 镜像是由多个文件系统(只读层)叠加而成,每个层仅包含了前一层的差异部分.当我们启动一个容器的时候,Docker 会加载镜像层并在其上添加一个可写层.容器上所做的任何更改,譬如新建文件.更 ...

  9. 论文笔记 - GRAD-MATCH: A Gradient Matching Based Data Subset Selection For Efficient Learning

    Analysis Coreset 是带有权重的数据子集,目的是在某个方面模拟完整数据的表现(例如损失函数的梯度,既可以是在训练数据上的损失,也可以是在验证数据上的损失): 给出优化目标的定义: $w^ ...

  10. C#在Xp系统执行.exe程序的报错怎么查看原因

    我的电脑---->管理---->事件查看器----->应用程序,查看错误来源