第十九章. 任务调度-Snail Job-企业级分布式调度与工作流

第十九章. 任务调度 (Snail Job): 企业级分布式调度与工作流

摘要:本章我们将深入 RVP 5.x 架构中一个强大的组件——Snail Job 任务调度中心。RuoYi 4.x 版本在任务调度与工作流编排上的缺失,在 5.x 中得到了彻底的补强。我们将学习 Snail Job 的三大核心功能:定时任务、重试任务与工作流,并掌握其架构、配置与使用。

注意: 我们这一章节不会深入实现一系列任务,这都需要编写代码,我们只需要对他有一个简单了解即可

本章学习路径

我们将按照以下路径,从架构理念到功能实操,全面掌握 Snail Job:

任务调度 (Snail Job) 企业级分布式调度与工作流


19.1. 核心理念与架构

在深入配置任务之前,我们必须先理解 Snail Job 的运行模式。它不是一个简单的“Cron 库”,而是一个完整的、分布式的“调度服务端”,用于统一指挥和调度所有业务应用(客户端)。

19.1.1. 登录调度中心与界面概览

首先,我们必须确保 ruoyi-snailjob-server 模块已成功启动。该模块是独立于主业务 (ruoyi-admin) 的调度服务端。

启动后,在 RuoYi-Vue-Plus 界面点击“系统监控” -> “任务调度中心”,即可打开 Snail Job 的登录页。

默认凭证

  • 用户名:admin
  • 密码:admin

登录后,我们进入主概览页面。顶部最关键的元素是 命名空间切换,它允许我们在 development (开发) 和 production (生产) 等不同环境间切换。
image-20251113121459684

19.1.2. [核心] 三大功能:定时、重试与工作流

Snail Job 提供了三大核心能力,解决了传统单体调度(如 Quartz)在分布式和业务编排上的短板:

核心功能解决的痛点核心价值
定时任务Quartz 在分布式集群中的配置复杂,难以统一管理和监控。提供统一的 UI,管理所有业务集群的定时任务,并提供丰富的路由策略。
重试任务外部接口调用因网络抖动瞬时失败,需要复杂的代码来重试。自动化的失败重试机制,将业务从“瞬时故障”中解放出来,保障任务最终成功。
工作流多个任务间有复杂的依赖关系(如 A-> B-> C),难以编排。提供可视化拖拽界面,将多个定时任务编排成一个“有向无环图”(DAG)。

19.1.3. 架构解析:Server 与 Client 的 Netty 通信

Snail Job 采用的是 服务端/客户端 (Server/Client) 架构模式。

  • Snail Job Server (服务端):ruoyi-snailjob-server 模块。职责是“发号施令”,存储任务信息、管理客户端列表、按时触发调度。
  • Snail Job Client (客户端):内嵌在 ruoyi-admin 模块中。职责是“执行任务”,向 Server 注册自身,接收 Server 的调度指令并执行具体的业务代码(Java 方法)。

Server 和 Client 之间并非使用 HTTP 通信,而是采用 Netty 进行了长连接交互,以实现高性能的调度和实时日志回传。

核心端口约定

组件交互方式默认端口说明
ruoyi-snailjob-serverHTTP 服务8800Admin UI 界面访问端口
ruoyi-snailjob-serverNetty 服务17888[关键] 等待 Client 连接的端口
ruoyi-admin (Client)HTTP 服务8080业务应用端口
ruoyi-admin (Client)Netty 服务28080[关键] 客户端 Netty 端口(默认 20000 + 8080)

19.1.4. [重点] 命名空间 (Namespace):隔离开发与生产

“命名空间”是 Snail Job 中最重要的 环境隔离 机制。

痛点:在没有命名空间时,开发环境(development)和生产环境(production)可能会共用同一个调度中心数据库。这会导致开发人员的测试任务在生产环境中被执行,引发灾难。

解决方案:Snail Job 通过命名空间将所有数据(包括任务、执行日志、客户端机器列表)进行 物理隔离

  • 我们在 development 空间下创建的任务,对 production 空间完全不可见。
  • 部署到生产环境的 ruoyi-admin 客户端,其配置中应指定连接 production 命名空间。

⚠️ 停!先思考 5 秒
切换命名空间后,为什么首页的“定时任务”和“在线机器”数量都清零了?

答案:因为数据是 物理隔离 的。developmentproduction 是两套完全独立的数据,切换后看到的“清零”状态,是 production 空间下的真实情况(即尚未配置任何任务或客户端)。

19.1.5. 组管理 (Group):服务隔离与 Token 认证

如果说“命名空间”隔离的是“环境”,那么“组”隔离的就是“服务”或“项目”。

一个“组” (Group) 通常代表一个独立的服务集群(例如 ruoyi-order-serviceruoyi-user-service)。在 RVP 中,默认使用 ruoyi-vue-plus 作为组名。

核心配置:Token
在组的管理界面,有一个关键配置 Token。这个 Token 必须与 Snail Job Client(即 ruoyi-adminapplication.yml)中配置的 snail-job.token 完全一致

文件路径ruoyi-admin/src/main/resources/application-dev.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--- # snail-job 配置
snail-job:
enabled: true
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
namespace: ${spring.profiles.active}
# 随主应用端口漂移
port: 2${server.port}
# 客户端ip指定
host:

image-20251113121953498

如果 Token 不一致,Server 端会拒绝 Client 的注册请求,导致客户端无法接收调度任务。


19.1.6. 本节小结

我们理解了 Snail Job 的 Server/Client 架构、三大核心功能以及两大隔离机制(命名空间与组)。

核心要点

  • Snail Job 是一个独立的 Server,通过 Netty 与内嵌在业务应用中的 Client 通信。
  • 三大功能是:定时任务、重试任务、工作流。
  • “命名空间”用于隔离“环境”(如 dev vs prod)。
  • “组”用于隔离“服务”,并通过 Token 进行身份认证。

19.2. 核心功能(一):定时任务

在上一节中,我们已经掌握了 Snail Job 的整体架构。现在,我们开始深入其最基础、最常用的功能——定时任务管理。这相当于一个分布式的、可视化的 Cron 任务管理器。

19.2.1. 任务创建:核心参数详解

在“定时任务” -> “任务管理”中,点击“新增”,我们会看到创建任务的表单。

核心参数含义与说明
组名称选择该任务属于哪个服务集群(如 ruoyi-vue-plus)。
任务名称任务的业务描述(如“每日用户数据统计”)。
执行器类型通常选择 Java,表示执行一个 Java 方法。
执行器名称[核心] 对应 Client 端(ruoyi-admin)代码中 @Job 注解修饰的方法名。
方法参数传递给上述 Java 方法的参数(字符串形式)。
任务类型集群:任务只在集群中的 一台 机器上执行。
广播:任务在集群中的 所有 机器上同时执行。

执行器名称 (Executor Name) 是如何关联的?
我们可以假设我们模块中,有一个示例任务:

1
2
3
4
5
6
7
8
9
10
11
12
package org.dromara.job.snailjob;
@Component
@JobExecutor(name = "testJobExecutor")
public class TestAnnoJobExecutor {

public ExecuteResult jobExecute(JobArgs jobArgs) {
SnailJobLog.LOCAL.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
SnailJobLog.REMOTE.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
return ExecuteResult.success("测试成功");
}
}

要在 UI 上调度这个方法,执行器名称 就必须填写 demoJob

19.2.2. [重点] 调度策略:路由与阻塞

当任务类型选择为“集群”(Cluster) 时,Server 端需要决定“集群中有多台机器,到底由哪一台来执行?”,这就是 路由策略

路由策略决策逻辑
轮询按顺序在集群中选择一台机器执行。
LRULeast Recently Used。选择“最近最少被调用”的机器。
随机随机选择一台机器。
一致性哈希根据任务 ID 进行哈希,确保同一个任务尽可能落在同一台机器上。

image-20251113130054911

同时,如果一个任务的执行时间很长(如 5 分钟),但调度间隔很短(如 1 分钟),就会发生任务重叠。阻塞策略 决定了如何处理这种情况。

阻塞策略决策逻辑
丢弃(Discard) 本次任务重叠,丢弃本次调度,等待下次。
覆盖(Cover) 停止上一个正在运行的任务,执行本次任务。
并行(Parallel) 不管上一个任务是否完成,都立即开始执行本次任务。

image-20251113130136133

19.2.3. 触发机制:Cron 表达式与固定间隔

Snail Job 提供了四种触发类型,但我们只需要关注最核心的两种:

  1. 固定时间:例如,设置“60 秒” (s),任务将每 60 秒执行一次。
  2. Cron 表达式:用于定义复杂的定时规则(注意不是 “clone”)。
    • 0 0 2 * * ?:每天凌晨 2 点执行。
    • 0/10 * * * * ?:每 10 秒钟执行一次。

image-20251113130204124

19.2.4. 定时队列 (Batch):集群日志的查看

在“定时任务” -> “定时队列”中,我们可以看到所有的任务执行记录。

这里有一个重要概念:“队列” 。为什么不叫“日志”?因为在集群环境下,一次调度(一个批次)可能会在多台机器上执行(例如“广播”任务)。这个“批次”是父记录,它 聚合了 本次调度中所有相关机器的执行日志。

image-20251113130542602

点击批次 ID,我们可以看到详情,如果任务在多台机器上运行,这里会列出所有机器的地址及其对应的执行日志。


19.2.5. 本节小结

我们掌握了定时任务的创建、执行器(Java 方法)的关联、集群环境下的路由与阻塞策略,以及如何查看执行批次日志。

核心要点

  • “执行器名称”是核心,它必须与 Client 端 Java 代码中 @Job(name = "...") 的值一致。
  • “集群”任务只执行一次,“广播”任务在所有机器上执行。
  • “路由策略”解决“哪台机器执行”的问题;“阻塞策略”解决“任务重叠”的问题。
  • “执行批次”是聚合了集群中所有机器执行日志的父记录。

19.3. 核心功能(二):工作流 (Workflow)

在上一节中,我们已经掌握了如何调度和执行 单个 的定时任务。但在复杂的业务场景中,任务之间往往存在依赖关系。例如,必须先执行“任务 A:拉取订单数据”,A 成功后,才能执行“任务 B:统计订单报表”,如果 A 失败,则需要执行“任务 C:发送失败告警”。

这种对任务的 编排,就是“工作流”功能的核心价值。

19.3.1. 工作流的价值:可视化任务编排

Snail Job 的工作流提供了一个可视化的拖拽界面,允许我们将多个独立的“定时任务”组合成一个有向无环图 (DAG)。

我们不再需要用代码去硬编码复杂的依赖逻辑,而是通过“拖拽”和“连线”来定义业务流程。

19.3.2. 节点详解(一):任务节点

在工作流画布中,最基础的单元是“任务节点”。

当我们拖入一个“任务节点”时,需要配置它“具体执行哪个任务”。这里的“任务”,必须是已在“定时任务”列表中 预先定义好 的(例如我们上一节的 demoJob)。

工作流本身不定义任务的执行内容,它只负责 定义任务的执行顺序

19.3.3. 节点详解(二):决策节点 (SpEL)

工作流的强大之处在于它支持 条件分支,这是通过“决策节点”实现的。

一个“决策节点”允许我们定义一个或多个分支,每个分支都包含一个 SpEL 表达式 (Spring Expression Language)

它是如何工作的?

  1. 上一个“任务节点”(如 demoJob)在执行完毕时,可以返回一个结果(例如一个 JSON 字符串)。
  2. “决策节点”会获取这个返回结果,并使用 SpEL 表达式对其进行判断。
  3. 例如,任务返回了 {"code": 200, "userId": 1}
  4. 决策节点的分支可以写:
    • 分支 A (成功):#taskResult.code == 200
    • 分支 B (失败):#taskResult.code != 200
  5. 工作流会根据表达式的 true / false 结果,自动流转到对应的分支(例如执行“成功”路径上的任务 B,或“失败”路径上的任务 C)。

19.3.4. 查看执行详情:追踪编排路径

当一个工作流执行完毕后,我们可以在“工作流” -> “执行批次”中查看结果。

与定时任务不同,工作流的执行详情会 高亮显示 实际执行的路径。例如,如果 demoJob 执行失败,导致工作流走向了“失败告警”分支,那么这个路径在 UI 上会清晰地标识出来,帮助我们快速定位问题,我们简单了解即可


19.3.5. 本节小结

我们学习了 Snail Job 的工作流功能,它通过可视化编排和 SpEL 决策节点,将孤立的定时任务串联成复杂的业务流程。

核心要点

  • 工作流的价值在于“编排”,它解决的是任务之间的“依赖”和“顺序”问题。
  • “任务节点”必须引用一个已在“定时任务”中创建的任务。
  • “决策节点”使用 SpEL 表达式,根据上一个任务的返回结果,实现条件分支(如成功/失败)。

19.4. 核心功能(三):重试任务

在掌握了定时任务和工作流之后,我们来探索 Snail Job 提供的第三大“杀手锏”——重试任务。

19.4.1. 重试的价值:应对瞬时故障

痛点:在分布式系统中,我们的业务逻辑(例如 demoJob)经常需要调用外部 API 或其他微服务。这些调用可能因为网络抖动、对方服务瞬时高负载等原因导致“瞬时失败”。

如果不处理,这个任务就被标记为“失败”了。但这种瞬时故障,往往在 10 秒后重试一次就能成功。

解决方案:“重试任务”模块就是为了解决这个问题而生的。它提供了一个 独立于定时任务 的、专门的、健壮的重试机制。当我们的业务代码(不一定是定时任务,任何 Java 方法都可以)在执行中捕获到“可重试”的异常时,可以将它提交给 Snail Job 的重试队列。Snail Job 会在后台按照配置的策略(如 1 分钟后、5 分钟后、15 分钟后)自动发起重试,直到成功。

重要区别:定时任务配置中的“最大重试次数”,指的是 调度失败(如 Server 无法连接 Client)的重试;而“重试任务”模块,解决的是 业务逻辑执行失败(如 API 调用超时)的重试。

19.4.2. [核心] 四大概念:场景、任务、日志与死信

要理解重试模块,必须先弄清它的四个核心菜单(概念)及其关系:

  1. 重试场景 (Scene)
    • 是什么:定义了一个“业务类别”,例如“订单支付回调”或“同步用户信息”。它是一类重试任务的集合。
  2. 重试任务 (Task)
    • 是什么:一个具体的、待重试的业务操作。例如,“回调订单号 10086”失败了,它会作为一条“任务”进入“订单支付回调”这个“场景”中。
  3. 重试日志 (Log)
    • 是什么:记录了 Snail Job 对“重试任务”每一次尝试的日志。例如,10:00 尝试失败,10:05 尝试失败,10:15 尝试成功。
  4. 死信任务 (Dead-Letter Task)
    • 是什么:当一个“重试任务”达到了配置的“最大重试次数”后(例如 10 次),如果依然失败,Snail Job 会放弃重试,并将该任务转储到“死信”列表中。
    • 为什么:这是一种保护机制,防止无限重试,并允许开发人员“手动介入”处理这些“真正失败”的任务。

它们的关系是:一个“场景”下,会产生多个“任务”;每个“任务”的执行过程,会产生多条“日志”;当“任务”重试耗尽后,会变为“死信任务”。

19.4.3. [重点] 声明式集成:基于注解的自动注册

与“定时任务”不同,Snail Job 的“重试任务”和“重试场景”一般不推荐在 UI 上手动新增

Snail Job 提倡的是一种“代码优先”的声明式集成。开发者在业务代码中(例如 ruoyi-admin)通过 添加注解 的方式,来定义一个重试方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这是一个示例注解,非 Snail Job 真实注解,仅为说明概念
// @RetryScene(name = "orderCallbackScene") // 1. 定义一个重试场景
// public class OrderService {
//
// @Retryable(scene = "orderCallbackScene", maxAttempts = 5) // 2. 将方法注册为可重试
// public void callback(Order order) {
// // 3. 尝试调用外部 API
// // ...
// if (callApiFailed) {
// throw new RuntimeException("API a_call_failed"); // 4. 抛出异常,Snail Job 自动接管重试
// }
// }
// }

ruoyi-admin (Client) 启动时,Snail Job Client 会自动扫描这些注解,并 自动将“场景”和“任务”注册 到 Snail Job Server 的 UI 界面上。

这种方式的优势在于,业务逻辑和重试策略在代码中是“高内聚”的,我们无需在 UI 和代码库之间来回切换和维护两套配置。


19.4.4. 本节小结

我们理解了 Snail Job 重试模块的核心价值、四大概念(场景、任务、日志、死信)之间的关系,以及它所提倡的“基于注解的声明式”集成方式。

核心要点

  • 重试任务的核心价值是:解决业务逻辑中的“瞬时故障”(如网络抖动)。
  • 它的四大组件关系为:场景 -> 任务 -> 日志 -> 死信。
  • 最佳实践是通过在 Client 端(业务代码)使用注解来“声明”重试,Snail Job 会自动将其注册到 Server 端。
  • “死信任务”是重试达到上限后依然失败的产物,需要人工介入。

19.5. 运维与管理

在掌握了 Snail Job 的三大核心业务功能(定时、工作流、重试)之后,我们还需要确保这套系统能够被安全、稳定地运维。本节我们将学习 Snail Job 提供的运维管理功能,包括告警、权限和监控概览。

19.5.1. 告警通知:配置多渠道告警

一个健壮的调度系统,必须具备“主动发现问题”的能力,而不是等待用户反馈“任务没执行”。“告警通知”就是实现这一目标的核心。

Snail Job 允许我们配置精细化的告警规则,在任务执行失败或客户端异常时,主动通知相关人员。

配置分为两步

第一步:配置通知人(“通知谁”)
在“通知人”菜单中,我们可以定义接收告警的渠道和目标。

  • 渠道支持:Snail Job 支持钉钉、企业微信、飞书、邮箱和通用 Webhook。
  • 配置:例如,选择“邮箱”,需要输入接收人的邮箱地址;选择“钉钉”,则需要输入钉钉群机器人的 Webhook 地址。
  • 注意:使用“邮箱”渠道,必须先在 Snail Job Server 端(ruoyi-snailjob-server)的 application.yml 中配置好 SMTP 发件服务器信息。

第二步:配置通知列表(“何时通知”)
在“告警通知”菜单中,我们创建告警规则,将其与“通知人”关联起来。

  • 组名称:选择要监控的组(如 ruoyi-vue-plus)。
  • 任务类型:选择要监控的类型(如“定时任务”、“工作流”)。
  • 通知场景[核心] 选择触发告警的条件,例如 执行失败客户端执行失败 等。
  • 通知人:选择第一步中配置好的通知人。

通过这套配置,我们就可以实现“当 ruoyi-vue-plus 组的 demoJob 任务 执行失败 时,自动发送 钉钉 消息给 开发A组”的自动化运维。

19.5.2. 权限管理:用户与组的权限分配

默认情况下,我们使用 admin 用户登录,它拥有系统的最高权限。但在企业环境中,我们不能把 admin 账号共享给所有人。

“用户管理”功能允许我们创建新的操作员账号,并为其分配精细化的权限。

  • 管理员:如果勾选“管理员”,该用户将拥有和 admin 一样的所有权限。
  • 普通用户:如果不勾选“管理员”,我们可以为其指定“组”的权限

例如,我们可以创建一个“业务A组”的用户,并只为他分配 ruoyi-group-A 这个组的管理权限。这样,该用户登录后,就只能看到和操作 ruoyi-group-A 相关的任务,无法查看其他组(如 ruoyi-group-B)的任何信息,实现了权限的严格隔离。

19.5.3. 关键指标概览:任务、批次与机器

Snail Job 的“首页”仪表盘是运维人员最常访问的界面。它提供了四大核心指标的概览:

指标模块核心数据运维价值
定时任务总次数、成功、失败、停止、取消快速查看定时任务的整体健康度,失败占比是关键。
重试任务总次数、成功、运行中、最大重试、暂停最大重试暂停 的数量,预警了可能转为死信任务的风险。
工作流总次数、成功、失败、停止、取消监控核心业务流程(工作流)的执行情况。
总在线机器服务端数量、客户端数量[核心] 监控 Client 存活。如果 ruoyi-admin 集群有 3 台,这里却只显示 2 台,说明有 1 台已掉线。

此外,首页的统计图表还展示了最近一周的任务执行趋势,帮助我们分析任务的成功率波动。


19.5.4. 本节小结

我们学习了 Snail Job 在运维侧的告警、权限和监控功能。这些功能是保障调度中心在生产环境中稳定、安全运行的基石。

核心要点

  • “告警通知”通过“通知人”+“通知规则”的组合,实现对失败任务的主动监控。
  • “用户管理”可以创建普通用户,并按“组”对其进行权限隔离。
  • “首页”仪表盘是核心监控入口,特别是“总在线机器”指标,用于监控 Client 端的存活状态。