XXL-JOB、PowerJob 与平台边界:什么时候该把任务交给调度中心
控制台上的“暂停任务”和“修改参数”,最终要作用到某个 Java handler 或 Processor。调度平台保存计划、选择执行节点,并通过执行器协议调用处理程序;读取订单、调用业务服务和保存结果仍由应用完成。
XXL-JOB 以任务管理和执行器调度为主要入口;PowerJob 还提供 Map、MapReduce 和工作流等执行模型。选型时需要比较任务如何运行、失败如何重试以及团队是否承担得起控制中心的维护工作。
调度平台管理哪些对象
控制中心与执行器怎样协作
控制中心
├── 管理任务定义、时间规则和运行参数
├── 保存执行记录,选择执行节点
├── 发起任务,接收执行结果
└── 根据策略安排重试、告警和后续任务
执行器
├── 注册自身可达地址并报告状态
├── 找到任务对应的 Java 方法或 Processor
├── 执行业务逻辑,记录过程日志
└── 上报最终结果
业务存储
└── 保存订单、流水、输出文件和幂等操作结果注册时,执行器把自己的地址报告给控制中心。如果报告的是容器私有地址,而中心位于另一网络,节点会出现在列表中,触发连接却可能失败。连通性检查需要覆盖中心到执行器的调用,以及执行器到中心的注册和反馈。
执行协议和业务接口通常使用不同端口。XXL-JOB 的执行器内置服务接收调度调用;PowerJob 根据所选协议启动传输端口。应用已经能响应自己的健康接口,不能代替调度协议端口的可达性检查。
XXL-JOB 的任务和路由
XXL-JOB 将任务绑定到执行器组,BEAN 模式通过 @XxlJob 名称找到 Java 方法。一个应用可以注册多个 handler,控制中心每个任务再配置各自的参数和计划。
@XxlJob("settlementHandler")
public void settlement() throws Exception {
String operation = XxlJobHelper.getJobParam();
String result = Ledger.apply("xxl", operation);
XxlJobHelper.log(result);
if (result.startsWith("FAILED")) {
XxlJobHelper.handleFail(result);
} else {
XxlJobHelper.handleSuccess(result);
}
}Spring 应用可以使用对应的 Spring 执行器实现。实验使用官方 XxlJobSimpleExecutor 注册同样的注解方法,不要求搭建 Web Controller。handler 名称、执行器 appname 和网络地址是三个不同配置,名称写错时应从控制中心参数与执行器注册日志逐项核对。XXL-JOB 固定版本源码
路由策略决定从组内地址中选择执行器:
| 策略类型 | 作用 | 选择时关注 |
|---|---|---|
| FIRST、LAST | 固定选择地址列表中的一个位置 | 列表顺序变化可能改变目标 |
| ROUND、RANDOM | 轮询或随机分配 | 节点状态是否可共享 |
| CONSISTENT_HASH | 依据任务等信息保持相对稳定分配 | 不能替代业务数据分片 |
| FAILOVER、BUSYOVER | 探测后选择可用或不忙的执行器 | 探测成功到实际调用仍有时间差 |
| SHARDING_BROADCAST | 给多台执行器传递分片参数 | handler 必须按参数限制数据范围 |
阻塞策略处理同一执行器上已有任务尚未完成的情况。单机串行会排队;丢弃后续调度会拒绝新请求;覆盖之前调度会尝试停止旧执行并清理排队工作。覆盖策略涉及线程中断,旧业务调用是否真正停止仍取决于代码和下游行为。
单机串行不能约束其他执行器上的同一业务,也不能阻止人工创建另一条任务操作相同数据。跨节点去重应由业务操作键和数据库约束实现。XXL-JOB 功能与策略说明
PowerJob 的实例、TaskTracker 与 Processor
PowerJob 区分任务定义、某次运行实例以及实例内部的任务。Server 管理计划和实例;Worker 中的运行组件组织具体执行,Processor 实现业务代码。
Job 定义
└── Instance:本次运行
├── TaskTracker:管理实例内任务分发和结果
├── Task 1 → Processor
├── Task 2 → Processor
└── 汇总结果或后续步骤不同执行模式有不同的数据处理方式:
| 模式 | 处理方式 |
|---|---|
| STANDALONE | 在一个执行节点上运行处理器 |
| BROADCAST | 多个节点分别执行,适合节点本地维护等任务 |
| MAP | 主任务拆成子任务,交给工作节点执行 |
| MAP_REDUCE | 拆分子任务后,再汇总子任务结果 |
| Workflow | 按 DAG 组织多个作业及其依赖 |
采用 MapReduce 时,处理器需要先表达如何拆分输入。每个子任务读哪一段数据、如何序列化参数、失败后重做多大范围,以及汇总结果能否装入内存,都由这个设计决定。只切换执行模式,原来的 SQL 查询仍缺少分工规则。PowerJob 执行模型
基础 Processor 可以直接返回成功或失败:
public class SettlementProcessor implements BasicProcessor {
@Override
public ProcessResult process(TaskContext context) throws Exception {
String result = Ledger.apply("power", context.getJobParams());
context.getOmsLogger().info(result);
return new ProcessResult(!result.startsWith("FAILED"), result);
}
}任务级重试和实例级重试的范围不同。一个大实例拆成很多子任务后,重做失败子任务与重新运行整个实例的成本差异很大;同时开启两层重试时,要计算最坏调用次数,并保证业务写入能够去重。
什么时候需要独立调度中心
与应用内调度、批处理框架的分工
少量随应用启动和关闭的维护任务,使用 Spring 调度可能已经足够。需要动态暂停、集中日志、多执行节点和统一重试策略时,独立调度平台更有价值。
| 需求 | 主要选择 |
|---|---|
| 进程内定期刷新、小规模后台维护 | Spring TaskScheduler |
| 持久计划、复杂 Trigger、Java 内嵌调度 | Quartz |
| 多应用集中任务管理和执行器调度 | XXL-JOB 或 PowerJob |
| 可恢复的大批数据处理、chunk 检查点 | Spring Batch,外部平台负责启动 |
| 多阶段依赖和跨任务编排 | 平台工作流或专门工作流引擎 |
平台增加了数据库、管理服务、执行器协议、账号权限和升级流程。只有定时触发需求的服务,不必为了使用某个平台而引入整套运行设施。
对比时检查实际运行模型
XXL-JOB 的执行器组和 handler 方式便于把已有 Java 业务方法接入管理界面。PowerJob 的执行模型适合需要子任务分发和汇总的处理,但也需要理解 Worker 内部的任务管理、存储和资源限制。
选型时可用同一个 handler 检查完整运行过程:注册后手动触发,查到成功结果,再制造业务失败并观察重试,最后重启服务核对保存的记录。容器地址能否被中心访问、执行器使用什么身份、日志保存多久,也都要在部署环境里确认。
PowerJob 5.1.2 新增 MU 协议用于特定单向网络场景,但该版本发布说明标注为 BETA、仅建议开发环境使用。HTTP、AKKA 和 MU 的网络要求不同,不能因为存在一个新协议就假定现有生产网络无需改造。PowerJob 5.1.2 发布说明
平台高可用与业务完成
部署多个控制中心,可以减少单个管理节点故障带来的停机风险,但共享数据库仍然重要。数据库连接池耗尽、锁等待或磁盘写满,可能影响所有调度节点。
执行节点故障后,平台可以探测失联并重试。原执行者可能已经提交业务,只是结果回调未到达。恢复执行应继续使用相同业务键,以便查询或复用已有结果。
日志也需要区分阶段。XXL-JOB 的触发结果和处理结果分别表示派发及执行反馈;PowerJob 的运行实例有自己的状态和结果。日志记录应关联业务操作编号,才能从平台执行记录找到真正的业务流水。
启动两个真实平台并验证重试
环境和身份
下载完整平台实验工程。需要 Linux、Bash、Docker Engine、Compose v2、curl、jq 和 unzip,宿主用户需要 Docker 使用权限。
| 组件 | 实验设置 |
|---|---|
| XXL-JOB Admin / Core | 3.4.2 |
| PowerJob Server / Worker | 5.1.2 |
| MySQL | 8.4.11 |
| Maven | 3.9.12 |
| Worker Java | 17.0.20+8 |
| XXL-JOB 官方镜像 Java | 17.0.19+10 |
| PowerJob 派生运行镜像 Java | 17.0.20+8 |
| 应用容器身份 | 10001:10001 |
PowerJob 官方 v5.1.2 镜像实际包含 Java 8u292。工程的多阶段 Dockerfile 从该镜像提取原版 Server JAR,再放入精确版本的 JDK 17 镜像运行。它没有修改 PowerJob 代码,也没有包含动态 Java 容器编译所需的 Maven;实验使用已经构建好的本地 Processor。
两套平台顺序运行,共用一个隔离 MySQL 服务,使用 xxl_job 和 powerjob 两个数据库。XXL 管理端只绑定 127.0.0.1:18221,PowerJob 只绑定 127.0.0.1:18222。数据库与执行器协议端口仅在 Compose 网络内可达。
应用使用单独的 platform 数据库账号。初始化脚本使用数据库 root 创建两个库和授权;MySQL 镜像完成初始化后由自身数据库进程身份运行。Maven 采用宿主 UID/GID,缓存位于当前用户创建的目录。
构建和初始化
unzip xxljob-powerjob-lab.zip
cd xxljob-powerjob-lab
bash prepare.shprepare.sh 依次构建两个独立 SDK 工程、启动 MySQL、下载固定版本的 XXL-JOB 初始化 SQL、创建实验业务表,并构建 PowerJob 运行镜像。
脚本检查目标库已有表时会拒绝继续,防止重复初始化覆盖现有平台。成功后输出:
platform schemas and worker binaries ready若 Maven 下载失败,先检查依赖仓库和代理配置;若 MySQL 未健康,查看 docker compose -p platform20 logs mysql。镜像拉取失败时不要随意改成其他版本,因为数据库结构、控制台接口和 SDK 都可能不同。
lab_attempt 保存故障注入的尝试次数,lab_effect 按 (platform,op) 唯一约束保存业务效果。三个操作参数分别是:
| 参数 | 处理器行为 |
|---|---|
success:A | 正常提交一条结果 |
retry:A | 第一次返回失败,后续尝试成功 |
fail:A | 每次都返回失败 |
尝试次数随 MySQL 事务保存。Worker 重启后会继续读取这份记录,故障注入不会因 JVM 中的计数器重新从零开始。
XXL-JOB:注册、触发与回调
bash verify-xxl.sh脚本启动真实 Admin 和 Worker,使用初始化管理员账号登录,等待执行器组出现 http://xxl-worker:9999,再创建任务:
| 配置项 | 值 |
|---|---|
| 执行器 appname | scheduling-xxl-lab |
| handler | settlementHandler |
| 调度方式 | NONE,由实验手动触发 |
| 路由 | FIRST |
| 阻塞策略 | SERIAL_EXECUTION |
| 超时 | 10 秒 |
| 失败重试次数 | 1 |
| 执行器 token | 隔离实验中的同一固定值 |
可以打开 http://127.0.0.1:18221,使用 admin / 123456 登录检查任务及调度日志。这个初始账号来自官方 SQL,必须在任何共享部署前更换。
该版本的登录路径是 /auth/doLogin,不能直接复制旧版本的 /login。登录请求是表单,成功响应需要同时检查 HTTP 与业务字段。XXL-JOB 登录实现
mkdir -p .downloads
curl -q --noproxy '*' --fail-with-body -sS \
-c .downloads/xxl.cookie \
--data-urlencode userName=admin \
--data-urlencode password=123456 \
http://127.0.0.1:18221/auth/doLogin |
jq -e '.code == 200'失败负例可把密码改为 wrong-password。HTTP 可能仍返回 200,但 code 不再是 200;jq -e 会非零退出。此时应修正身份,不应继续拿空 cookie 调用任务接口。
任务 ID 从创建接口响应中取得,不能把某台机器上的固定编号写死。已知有效 ID 后,触发请求形态为:
JOB_ID=5 # 改为当前平台创建接口返回的 ID
curl -q --noproxy '*' --fail-with-body -sS \
-b .downloads/xxl.cookie \
--data-urlencode "id=$JOB_ID" \
--data-urlencode executorParam=success:A \
--data-urlencode addressList= \
http://127.0.0.1:18221/jobinfo/trigger |
jq -e '.code == 200'verify-xxl.sh 会自动取得 ID,不需要手动修改。触发接口成功后,任务尚可能在队列里等待;应继续查询执行日志。任务创建与触发接口
三个操作最终产生五条处理日志:
| 参数 | 触发结果 | 处理结果 | 说明 |
|---|---|---|---|
success:A | 200 | 200 | 首次成功 |
retry:A | 200 | 500 | 第一次故障注入 |
retry:A | 200 | 200 | 平台重试成功 |
fail:A | 200 | 500 | 第一次失败 |
fail:A | 200 | 500 | 重试后仍失败 |
脚本逐个运行三个场景,等当前场景产生预期的最终处理记录后再发起下一个,以便单独观察成功与重试。控制台操作、其他计划或额外故障也可能增加尝试次数,因此应使用没有其他触发来源的隔离任务。脚本核对各场景状态、尝试总数和业务结果数,最后输出:
XXL: success=1 retryAttempts=2 permanentFailureAttempts=2 businessEffects=2PowerJob:应用、实例与 Processor
bash verify-power.sh脚本先停止 XXL 的两个应用容器,避免同时占用过多内存,然后启动 PowerJob。使用 ADMIN / local-power-admin 登录,创建 scheduling-power-lab 应用,再启动 Worker。大写 ADMIN 是该版本初始化的管理员名称。
控制台地址为 http://127.0.0.1:18222。默认 namespace 中能看到实验应用及在线 Worker;任务使用 STANDALONE、BUILT_IN 和 example.platform.SettlementProcessor,实例重试次数为 1,任务级重试次数为 0。
控制台接口通过 PowerJwt 请求头传递登录令牌,应用相关操作还需要 AppId。脚本从实际登录响应读取 token,不把令牌打印或写进正文。若把 HTTP 200 当作登录成功,会漏掉返回 JSON 中的 success:false。PowerJob 身份与登录实现
运行记录的业务结果为:
| 任务 | 尝试次数 | 实例最终状态 | 结果 |
|---|---|---|---|
lab-success:A | 1 | 成功 | effectInserted=1 |
lab-retry:A | 2 | 成功 | 第二次提交结果 |
lab-fail:A | 2 | 失败 | 没有业务结果 |
在本版本数据库中,实例状态 5 表示成功、4 表示失败。页面使用对应文字展示;排障时应按固定版本的枚举解释数字。PowerJob 实例状态枚举
脚本输出:
PowerJob: success=1 retryAttempts=2 permanentFailureAttempts=2 businessEffects=2核对业务表并检查重启
平台结果与业务表一起核对:
docker compose -p platform20 exec -T \
-e MYSQL_PWD=local-root-only mysql \
mysql -uroot -e \
"SELECT platform,op,attempts FROM xxl_job.lab_attempt;
SELECT platform,op FROM xxl_job.lab_effect;"两个平台各有两条业务结果:success:A 和 retry:A。失败任务只有尝试记录,没有效果记录。
重新启动 PowerJob Server:
docker compose -p platform20 restart power数据库里的已有实例结果保留。等 Worker 重新报告状态后,再从控制台运行成功任务:平台新增一次执行,处理器得到 effectInserted=0,业务表仍保留原来那条效果记录。重复调用已经发生,唯一键阻止了第二次插入。
保留数据结束实验:
docker compose -p platform20 --profile '*' down--profile '*' 同时包含两个平台及其 worker,避免只停止默认服务 MySQL。确认不再需要任务定义、执行记录和业务结果后,才使用 docker compose -p platform20 --profile '*' down -v 删除本项目数据库卷。
网络、失败与生产部署
按通信阶段排查
| 现象 | 优先检查 | 下一步 |
|---|---|---|
| 执行器不在列表 | appname、注册地址、token、应用是否已创建 | 查看注册请求及控制中心日志 |
| 列表在线但触发失败 | 中心到执行器地址和协议端口 | 从中心所在网络测试目标可达性 |
| 触发成功但无最终结果 | Worker 日志、阻塞调用、回调连接 | 查询业务表,判断是否已提交 |
| 每次都立即失败 | handler 名称或 Processor 类、参数、依赖 | 核对控制台配置与实际构建产物 |
| 失败重试次数异常多 | 平台、应用与 SDK 是否叠加重试 | 汇总同一业务键全部尝试 |
| 重启后任务定义消失 | 持久卷、连接库名、初始化脚本 | 核对实际挂载和数据库实例 |
Docker 中的 127.0.0.1 指当前容器。Worker 注册地址若写成 127.0.0.1:9999,控制中心会连接自己。Compose 内使用服务名,跨主机则使用其他节点可达的地址,并限制允许访问的来源。
鉴权失败与网络失败也要分开。连接拒绝说明服务或端口不通;HTTP 401/403 或业务 JSON 拒绝说明请求已到达某个服务,需要核对 token、账号和权限。不要通过关闭鉴权长期解决配置错误。
高可用部署需要共同的数据和一致配置
多个控制中心需要按产品要求共享调度数据库,并保持任务配置、协议和集群参数一致。数据库账号权限、连接数、连接超时和备份恢复也要纳入运维。
执行器扩容之前,应确认处理器不依赖节点私有状态。结果只保存在容器临时目录时,下一次路由到另一台机器就无法读取。需要保留的输入、检查点和业务结果应放在共享且有持久策略的存储中。
计划暂停、运行终止和删除任务是不同动作。暂停通常阻止后续触发,已派发任务可能继续运行;终止请求涉及执行器中断与业务清理;删除定义还可能影响后续审计。生产操作应展示这些差异,并限制权限。
脚本和动态代码任务的权限
调度平台能够执行 Java 方法、Shell 或其他脚本,控制台写权限因此可能间接获得执行器的系统权限。平台不能暴露到公网只靠默认口令保护,也不应让所有任务共享生产管理员凭据。
常见部署措施包括管理入口身份认证、按应用授权、执行器网络隔离、最小数据库权限、非 root 应用身份和敏感操作审计。脚本参数需要校验,避免把未经处理的用户输入拼进 shell 命令。
动态 Java 容器或在线脚本增加了构建工具、依赖下载和代码执行的风险。确实需要时,应独立设计构建网络、制品来源、资源限制和隔离方式。普通 BEAN/Processor 任务可以在发布流水线中构建审核后再部署。
迁移时保留业务身份
从本地调度迁移到平台,先停用旧计划,再启用新计划,并检查是否仍有旧实例执行。两个入口并行试运行时,要共用业务幂等键。
逐项迁移原计划的时区、迟到处理、串行范围、超时和重试设置,再核对参数来源及分片规则。历史窗口需要补跑时,把窗口参数一并录入;新平台无法仅从一条 Cron 推断过去缺失了哪些业务结果。
权威资料与规范地址
XXL-JOB 调度策略与管理端实现
- XXL-JOB 固定版本源码:https://github.com/xuxueli/xxl-job/tree/3.4.2
- XXL-JOB 功能与策略说明:https://github.com/xuxueli/xxl-job
- XXL-JOB 初始化 SQL:https://raw.githubusercontent.com/xuxueli/xxl-job/3.4.2/doc/db/tables_xxl_job.sql
- XXL-JOB 登录实现:https://github.com/xuxueli/xxl-job/blob/3.4.2/xxl-job-admin/src/main/java/com/xxl/job/admin/framework/controller/LoginController.java
- XXL-JOB 任务创建与触发:https://github.com/xuxueli/xxl-job/blob/3.4.2/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobInfoController.java
PowerJob 执行模型与实例状态
- PowerJob 执行模型:https://github.com/PowerJob/PowerJob
- PowerJob 5.1.2 发布说明:https://github.com/PowerJob/PowerJob/releases/tag/v5.1.2
- PowerJob 身份与登录:https://github.com/PowerJob/PowerJob/blob/v5.1.2/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AuthController.java
- PowerJob 实例状态枚举:https://github.com/PowerJob/PowerJob/blob/v5.1.2/powerjob-common/src/main/java/tech/powerjob/common/enums/InstanceStatus.java
