Lazy loaded image
🗒️Xxl-Job
Words 2649Read Time 7 min
2025-9-5
2025-9-14
type
status
date
slug
summary
tags
category
icon
password
原文

一、整体架构

图1 xxl-job 整体架构
图1 xxl-job 整体架构
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 执行器管理界面
图2 执行器管理界面

执行器在线状态自动变更

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

    1. 需在页面手动添加 job,并选择 job 的对应配置。
    1. 在 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执行

      notion image
      job的执行由 admin 端、application 端协作完成。
      1. admin端从 MySQL取出任务加入时间轮,由时间轮驱动取出任务向 Application 端 发起 RPC 调用并将 job 的执行日志写入 MySQL。
      1. application 端接受到请求加入队列,然后jobThread会从队列取出任务进行执行,执行完任务,向 admin 端发起 RPC 调用。
      1. admin 端接受到 callback 调用,更新job任务的执行结果。如果任务执行失败,monitorThread线程会从数据库中读取执行失败的任务进行重试操作。当然如果job配置了失败重试次数。
       
      admin 端执行 job,可以分为两个部分,预读和触发,分别由Admin端的两个线程(scheduleThreadringThread)来完成。
       

      预读

      预读中除了预读 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进行预读处理时,都会更新任务的该字段,以便下次预读进行查询。
      图3 schedlueThread线程处理任务流程
      图3 schedlueThread线程处理任务流程
       
      1. admin端xxl-job, admin JobScheduleHelper#ringThread 守护线程
          • 取出时间轮中的任务,然后执行任务

      触发

      任务由线程池去执行。
      1. 选择线程池 slow/fast threadPool
          • JobTriggerPoolHelper 中维护了2个线程池,快速线程池(fastTriggerPool)和慢速线程池(slowTriggerPool)。
          • 如果一个任务在当前分钟内超时超过 10 次,任务将会由慢速线程池去运行,否则使用快速线程池。
          • 每个job 都有一个timeout 计数器,如果一个 job执行时间超过 500ms 会被记录。
            • 超时500ms是系统默认值无法配置,500ms只是触发任务方法的执行时间并不是job 的真正执行时间。
              计数器数据结构为ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap,key为 jobId,value是超时次数。
              计数器记录当前分钟内所有运行过 job的超时次数。当时间超过当前分钟时,计数器会清空。
      1. 路由处理
      本质是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端请求的处理
      notion image
      • 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 直接执行远程调用逻辑)。
       
      上一篇
      Redisson 分布式锁
      下一篇
      Xxl-Job-路由

      Comments
      Loading...