数据平台与调度:真实 xxl-job 3.5.0 实验
教材正文:DAG 调度、持久状态、重试回填与恢复。本目录提供可复现的公开实验:真实 xxl-job 3.5.0 的 admin、执行器与 MySQL 表,跑一组有界任务,观察调度、执行、回调、重试、过期、注册、崩溃与恢复。
前置条件
| 依赖 | 要求 | 说明 |
|---|---|---|
| MySQL | 8.x,可用 root(可空密码)连接 127.0.0.1:3306 | 本机 DBngin 安装即可;可用 MYSQL_HOST/PORT/USER/PASSWORD/MYSQL_BIN 覆盖 |
| JDK | 17 或以上 | 优先 JAVA_HOME,否则 /usr/libexec/java_home -v 21 |
| Maven | 3.9.x | 在 PATH 或设置 MAVEN_BIN |
| 网络 | 首次运行 | 拉取固定 tag 源码与官方建表 SQL;之后走 ~/.cache/software-engineering-study/java-depth-impl/u85/ 缓存 |
不需要 Docker;不改用户既有 MySQL 库与实例配置;不访问外部 SMTP(告警邮件地址留空,mail 指向本机不存在的 11025 端口)。
一条命令
实验代码sh
python3 labs/data-scheduler/run.py退出码 0 表示 7 组场景全部通过,证据写到 evidence/u85-2026-10-27.json(含 exitCode、环境、场景观察、清理结果、执行器/admin jar 与建表 SQL 的 SHA-256)。每次运行都覆盖同一份证据文件:失败运行同样落盘(exitCode != 0),不会伪装成成功。
运行会做什么
- 用
doc/db/tables_xxl_job.sql建独有的库study_u85_xxl_<runid>(8 张表)。 - 从上游固定提交
3bebfc1609b95413581b11e270e5670dce679813(tag 3.5.0)构建xxl-job-admin。 - 构建
executor/下的教学执行器(xxl-job-core3.5.0 +XxlJobSimpleExecutor)。 - 启动 admin(随机端口)与执行器(随机端口),自动注册执行器组。
- 依次跑 7 组场景(见下),每组把真实 DB 状态与执行器 marker 文件交叉比对。
- 结束时停所有进程、
DROP DATABASE,并核对没有残留进程/库。
七个场景与真实观察
| 场景 | 观察 | 运行证据(2026-10-27,exit 0) |
|---|---|---|
DAG child_jobid | A→B→C 各 1 次;删除子任务后父任务仍成功但子任务无日志 | dagChain logs A=1 B=1 C=1;dagMissingChild parentHandle=200 childLogs=0 |
| 阻塞策略 | DISCARD_LATER 第二次触发 trigger_code=500, handle_code=0;SERIAL 两次都 200 | blockDiscard ... executorRejected=True / blockSerial serializedByMarker=True |
| 失败重试 | executor_fail_retry_count=2 时轨迹 500,500,200,retry_left=[2,1,0] | U85 retryCodes=[500,500,200] retryLeft=[2,1,0] |
| misfire | DO_NOTHING 错过 3 周期 0 条日志;FIRE_ONCE_NOW 恰好补 1 次 | misfireSkip logs=0 / misfireOnce logs=1 compensation=True |
| 优雅停机 | 在途 [job running, killed]、排队 job not executed;注册立即移除 | gracefulKill codes=[500,500] registryRemoved=True |
| 崩溃与丢失 | 崩溃后注册残留;回拨日志 11 分钟并刷新注册心跳跨过完整监控周期仍不判失败;注册过期后标"任务结果丢失";恢复后重新触发成功 | crash11minButRegistryAliveAfter70s handleCode=0 → crashLostJob handleCode=500 msg=任务结果丢失,标记失败 |
| 重复调度与切换 | 双 admin 22 秒只多 11 行(不是 22);杀 A 后 B 继续;全停重启后 15 秒 5 行、无突发补跑 | duplicateSameSecond=0 executorRepeatTriggers=0;restartResume newRows=5 |
边界与实验操作说明
- 丢失任务的 10 分钟阈值与 90 秒注册过期在实验中通过回拨
trigger_time/registry.update_time构造,这是对真实判据的受控加速,不是生产事实。 - misfire 的停机同样通过直接改库时间戳构造,不代表 admin 真的停了 65 秒。
- 未运行:COVER_EARLY 打断、
executor_timeout502、分片广播、故障转移/忙碌转移路由、多执行器实例分布;回调落盘重试(第四层)只有源码解释,未构造"执行器存活但回调不可达"实验;时间轮跨刻度去重未构造长耗时场景。 - 未覆盖拓扑:admin 与执行器分机器、跨机房 DB、网络分区、MySQL 主从切换、磁盘满。
- 本实验只跑单个 MySQL 实例;行锁
FOR UPDATE的多 admin 互斥依赖真实 DB,本实验用两个 admin 进程验证,但没有做 DB 不可用的容错验证。 - 任务参数与标签均为合成数据,不含用户业务数据。
目录
| 文件 | 用途 |
|---|---|
| run.py | 公开入口:建库、构建、起停进程、7 组场景、证据落盘 |
| executor/pom.xml | 教学执行器工程(xxl-job-core 3.5.0) |
| ExecutorMain.java | 执行器入口(frameless XxlJobSimpleExecutor,注册、接收、回调、优雅停机) |
| LabJobs.java | 4 个有界 handler:记录、失败重试、长任务、崩溃 |
| u85-2026-10-27.json | 一次完整运行的证据(exitCode、环境、观察、清理、SHA-256) |