type
status
date
slug
summary
tags
category
icon
password
原文
一、整体架构

MySQL: 用于保存执行器、系统用户、任务、日志、锁等信息
Admin:
调度中心,筛选任务向 Application 端发起请求调用 。
- 提供 http API 用于页面的展示和操作接口。
- 提供给 Application的 http API接口如注册执行器、更新日志等。
Application:
- 提供 job的执行入口。
- 可以集成 SpringBoot,也可以单独无框架集成运行。
EmbedServer:
Admin 、 Application项目启动时,会启动EmbedServer。
EmbedServer基于 netty框架、Http 协议来实现RPC,主要用于任务的调度。
application、admin 两端的通信都是基于 HTTP 协议,任务调度相关 API的都是EmbedServer提供。注册执行器、更新日志状态都是基于 SpringBoot 的 Http API。
执行器

执行器在线状态自动变更
对应图2中OnLine 机器地址,如果这里出现蓝色字样,表示application 端在线。
保活: Application 通过 http 请求上报信息给 admin 端
- application 启动时会创建一个名为
xxl-job, executor ExecutorRegistryThread
的守护线(ExecutorRegistryThread
),会将执行器名称、端口号、ip地址信息,通过 http 请求发送给 Xxl-Job-Admin。
- admin 端接受到请求后会去saveOrUpdate MySQL数据库。
admin 端在启动时会启动一个名称为
registryOrRemoveThreadPool
线程池,该线程池会在接受到 application 端的更新registry请求,执行更新MySQL表的请求。表名为
xxl_job_registry

xxl_job_registry
删除:
这里的删除会在 MySQL 物理删除该执行器。
- 主动删除:
- 页面有提供删除按钮
- 如果 Application端,通过 kill 15 这种方式来终止进程,则SpringApplicationShutdownHook(钩子方法)会触发
xxl-job, executor ExecutorRegistryThread
守护线程执行 registry-remove方法调用,目的是发起 http 请求给 Admin 端执行执行器的删除操作。
- 自动删除
Admin 端启动时会开启一个名为
xxl-job, admin JobRegistryMonitorHelper-registryMonitorThread
的守护线程,该线程会查询 xxl-job-registry表中的 update_time 超过 90 秒未更新的数据,然后直接物理删除该数据。Xxl-Job 执行器无自动发现的功能,需手动录入,能够自动更改的是调用地址(address_list)会更新(页面显示 Online机器地址)。
如果要更改自动发现,需修改源码。
- 在注册执行器的关联的步骤新增
xxl_job_group
表中相关数据,保持 xxl_job_group、xxl_job_registry
数据统一。
job
- 需在页面手动添加 job,并选择 job 的对应配置。
- 在 SpringBoot 中使用注解来表示任务
job 的持久化
- job通过页面手动配置保存在 MySQL中
xxl_job_info
表中。
- 每个job 都必须有一个 group,对应 MySQL中
xxl_job_group
。
xxl_job_group
表中存储了执行器的地址(address_list),在具体job执行时,会使用该地址来拼凑调用地址(addressUrl)。- job 的执行日志存储在 MySQL中 xxl_job_log 中。
路由策略
admin 端调用 application 端接口时会选择一个地址,所有的地址会存储在group 中。
路由策略就是选择要调用的 application端地址。
- 第一个
- 最后一个
- 轮询
- 随机
- 一致性 HASH
- 最不经常使用
- 最近最久未使用
- 故障转移
- 忙碌转移
- 分片广播
job执行

job的执行由 admin 端、application 端协作完成。
- admin端从 MySQL取出任务加入时间轮,由时间轮驱动取出任务向 Application 端 发起 RPC 调用并将 job 的执行日志写入 MySQL。
- application 端接受到请求加入队列,然后jobThread会从队列取出任务进行执行,执行完任务,向 admin 端发起 RPC 调用。
- admin 端接受到 callback 调用,更新job任务的执行结果。如果任务执行失败,monitorThread线程会从数据库中读取执行失败的任务进行重试操作。当然如果job配置了失败重试次数。
admin 端执行 job,可以分为两个部分,预读和触发,分别由Admin端的两个线程(
scheduleThread
、ringThread
)来完成。预读
预读中除了预读 5 秒内的任务外,还处理了任务的下次触发时间。
1.admin端
xxl-job, admin JobScheduleHelper#scheduleThread
守护线程。- 加锁
schedlueThread 线程进行预读前会通过
select * from xxl_job_lock where lock_name = 'schedule_lock' for update
语句来加锁,意味着同时只能有一个线程来进行预读操作。
- 预读5秒内(默认值)的任务,添加到时间轮,刷新任务的下次执行时间。
- xxl_job_info 保存了任务的信息,其中
trigger_next_time
是代表该任务的下次执行时间,每次对 job进行预读处理时,都会更新任务的该字段,以便下次预读进行查询。

- admin端
xxl-job, admin JobScheduleHelper#ringThread
守护线程 - 取出时间轮中的任务,然后执行任务
触发
任务由线程池去执行。
- 选择线程池 slow/fast threadPool
JobTriggerPoolHelper
中维护了2个线程池,快速线程池(fastTriggerPool)和慢速线程池(slowTriggerPool)。- 如果一个任务在当前分钟内超时超过 10 次,任务将会由慢速线程池去运行,否则使用快速线程池。
- 每个job 都有一个timeout 计数器,如果一个 job执行时间超过 500ms 会被记录。
超时500ms是系统默认值无法配置,500ms只是触发任务方法的执行时间并不是job 的真正执行时间。
计数器数据结构为ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap,key为 jobId,value是超时次数。
计数器记录当前分钟内所有运行过 job的超时次数。当时间超过当前分钟时,计数器会清空。
- 路由处理
本质是admin 端去调用 application 端的执行器,先要根据路由规则去构建远程调用地址。
- 每个 job 都会归属一个 group,group 里面会有所有可以调用的执行器的具体 ip和端口。
- 除了分片广播需要特殊处理,其他模式以策略模式构建返回调用地址。
策略模式的以抽象类
ExecutorRouter
为基础,其他路由方式继承并重写该类实现 route 方法。发起 RPC 调用
在经过预读、触发的流程后,已经明确了要调用的application的执行器,接下里Admin端会将请求URL和参数拼装好发起远程调用。
xxl-job 基于 netty框架、http 协议实现了一套RPC,用于执行器的执行。job 的执行调用的是 /run 接口。
application端执行 job
- 启动时的准备
application 启动时,会扫描
XxlJob
注解,将注解的方法包装成IJobHandler并保存KV 结构中。key为注解中的 name。value为对应的 JobHandler。job可以配置为 bean、GLUE模式。Bean模式则对应MethodJobHandler。
- 接受到Admin端请求的处理

- application 端接收到请求后,会根据请求参数中的 jobId,来判断是否有线程(jobThread)正在执行当前任务。
- job 默认的阻塞处理策略时单机串行,如果有jobThread正在运行任务,新任务会加入到 jobThread的队列中。
- 入列后,jobThread 线程会依次取出任务执行
- JobThread 继承 Thread,它会保存 jobHandler、执行的 jobId 集合等信息。
- jobThread 的队列保存的是admin 端发来的请求参数类,通过参数就能构建 job。
- 如果是 Job的运行模式是Bean模式,那么 jobHandler的具体实现是 MethodJobHandler,通过反射直接执行 job 中的代码。
- 执行完 job完后,会将任务入列(
callBackQueue
),triggerCallbackThread
线程会从队列中获取数据,想admin端发起远程调用(/callback接口)更改job日志 - 回调 admin端接口时,会保存日志。日志是以文本文件(.log)的形式来保存的。文件名对应着job日志的 id即 xxl_job_log 表中的主键例如9948.log。日志内容保存了该任务执行的日志(无论job执行成功或失败都会记录)。
如果向 admin 端的callback回调失败会记录日志,日志形式以文本形式(.log)保存,
.log 文件名为 xxl-job-callback-{时间戳}.log。日志中记录了logId、handleCode等信息。
callback回调失败指的是 handleCode ≠ 200
triggerRetryCallbackThread
线程会读取 callback 失败的日志文件,序列化成callback的参数,然后删除文件,继续尝试远程调用 admin callback接口。admin端处理callback
admin 端接受到/callback 的请求后,根据参数中的 jobId和 handleCode 更新 xxl_job_log 数据。handleCode=200 表示成功。
失败重试
monitorThread
(线程名:xxl-job, admin JobFailMonitorHelper)线程会从xxl_job_log中查询 1000条执行失败的id,根据记录中的 jobId查询job的信息,如果当前job的失败重试次数大于 0,则重试任务。如果job 设置了告警邮件则发送告警邮件。任务结果丢失处理
monitorThread(线程名: xxl-job, admin JobLosedMonitorHelper)调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
手动执行
job 可以手动执行调用的是http://xxxx:port/xxl-job-admin/jobinfo/trigger的接口
手动执行就是手动触发admin 端触发任务逻辑(slow/fast threadPool 直接执行远程调用逻辑)。
- Author:newrain-zh
- URL:https://alex.sh.cn/article/xxl-job
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!