系列:Spring AI + MCP------大数据智能运维实战
本篇目标:以模拟 Hive 作业为例,从准确的调度运行追到引擎记录;关联缺失或冲突时保留边界。
阅读基础:第七篇的 taskId、runId、attemptId;沿用
lab-07-12与 Java 17、Spring Boot 4.1.0、Spring AI 2.0.1。案例说明:全部标识、回执、状态和数据均为虚构;不连接真实 Hive、YARN 或调度平台。
1. 调度页面说 RUNNING,为什么引擎里还在等
"这次日报已经运行半个多小时了,Hive 一定算得很慢。"小周说。
小林指向调度页面:"这个 RUNNING 先说明执行器开始处理了。它是不是已经提交查询,提交以后在排队还是计算,还得往下查。"
小周换到引擎页面,搜出几个名字相近的应用:"选最新那个?"
"先别选。今天同一任务可能运行多次,一次运行还可能重试。名字相近、时间接近,都只能提供线索。"
上一篇解决了"哪一次运行、哪一次尝试"。本篇继续解决:这一次尝试,究竟对应哪条引擎执行记录。
观察时刻仍固定为 2026-09-17 09:40 +08:00 。本篇主样本是 run-current-0917,与上一章已经成功的 run-retry-0917 是两次不同的教学运行。
2. 先画一条能解释来源的关联链
为了便于理解,青禾教学平台设定了这样一条链路:
text
任务定义 daily-report
→ 运行 run-current-0917
→ 尝试 a1
→ 模拟 Hive 操作 demo-operation-20
→ 模拟查询 demo-query-20
→ 模拟引擎应用 demo-app-20
这不是把前一个标识改个前缀,就能推算出后一个标识。每一步都来自固定教学数据里的提交回执。

图 1:连线表示有回执支撑的关联,不表示所有 Hive 部署都必须经过同样的执行层次。
HiveServer2 提供客户端执行查询的服务,并支持操作日志;实际执行方式与配置会影响底层执行信息。本文借用这些概念说明如何关联证据,不实现 Hive 客户端。HiveServer2 官方文档
需要特别小心:一个查询不一定对应一个独占、全新的引擎应用。实际环境可能存在会话复用、多个执行阶段或不同执行模式。因此教学返回使用 links 和 applicationIds 列表,没有把"一次查询等于一个应用"写死在契约里。
当没有独立应用时,应该明确返回适用的执行信息或"不适用"。它和"本来应该有关联,但没有采集到"也是两种情况。本文的缺失样本属于后者。
3. 工具先接收运行与尝试,再返回它找到的关联
新增工具叫 ops_trace_engine,参数只有两个:
json
{
"runId": "run-current-0917",
"attemptId": "a1"
}
工具先验证 a1 确实属于这个运行,再查固定回执。它不接收任意 SQL,不让调用者传入一个文件路径,也不允许模型顺手提供一个"猜到的应用 ID"。
在 InvestigationTools.java 中,入口保持很薄:
java
return boundary.execute(context, "ops_trace_engine",
() -> service.trace(runId, attemptId));
共同边界负责身份、权限、调用额度和超时,服务负责对象与关联语义。这样同一个问题不会在五个工具里写出五套不同的判断。
主样本返回的关联部分如下,省略了外层请求元信息:
json
{
"mappingState": "LINKED",
"links": [{
"runId": "run-current-0917",
"attemptId": "a1",
"operationId": "demo-operation-20",
"queryId": "demo-query-20",
"applicationIds": ["demo-app-20"],
"source": "SIMULATED_SUBMISSION_RECEIPT",
"evidenceId": "run-current-0917/receipt-1"
}]
}
source 说明关联从哪里来,evidenceId 用于在解释中引用。真实接入时,可以映射到允许访问的回执或事件引用,但不要直接暴露内部连接串、任意文件路径或敏感 SQL 文本。
关联本身也需要时间和来源语义。不能拿昨天留下的一条应用记录,补到今天的另一次重试里。本文的整个回执集都属于同一固定教学快照,跨快照校验留给真实数据适配层处理。
4. 关联成功以后,两边状态仍然要分别解释
查询到关联,不代表两个系统的状态就可以合并成一个词。
主样本中:
| 来源 | 对象 | 已有事实 |
|---|---|---|
| 调度运行 | run-current-0917 | 09:00 入队,09:05 启动,观察时仍为 RUNNING |
| 提交回执与日志 | 尝试 a1 | 09:06 提交模拟操作,随后记录到查询与应用标识 |
| 引擎快照 | demo-app-20 | 09:39 上报 QUEUED |
| 队列快照 | demo-batch | 09:39 运行数 8,上限 8,原因 QUEUE_RUNNING_LIMIT |
这些记录并不矛盾。执行器可以一边保持 RUNNING,一边轮询尚未准入的引擎应用。
教学规则规定队列运行名额上限为 8。这里既有 8/8 的计数,又有引擎给出的 QUEUE_RUNNING_LIMIT 原因,因此可以说:"09:39 的引擎记录表明,这个应用仍在等待队列准入。"
但有两个范围不能越过。
第一,8/8 是教学队列的并发名额,不是集群 CPU 使用率,也不是 YARN 队列容量配置的通用表达式。
第二,09:39 是最近观测时刻。查询时钟是 09:40,不能省略这一分钟的时效差,也不能据此证明从 09:06 到 09:40 一直处于同一状态。
已经拿到了关联链,仍然要保留每一段证据自己的时间。
5. 关联缺失时,不要帮数据"补全剧情"
接着查询 run-unlinked-0917 的 a1。
它有调度运行与尝试记录,却没有可用的引擎提交关联。工具返回:
json
{
"mappingState": "MISSING",
"links": [],
"applications": [],
"nextCheck": "按本次 runId + attemptId 查提交回执"
}
小周问:"那是不是还没提交成功?"
还不能直接下这个结论。缺少回执可能是尚未提交,也可能是回执采集失败、适配没有覆盖该执行路径,或者记录暂时不可用。返回结果只证明当前没有取得可靠关联。
合适的回答是:"能确认执行器已启动,但当前缺少本次尝试到引擎对象的可靠关联,因此无法确认引擎阶段。下一步应核对该次尝试的提交回执和相关日志。"

图 2:缺失时补证据;冲突时先核对候选,不能把列表第一项当成答案。
这个工具返回成功,并不代表"链路完整"。查询可以成功地告诉我们关联缺失,这和数据源超时、请求没有执行成功是不同的情况。第十一篇会把它们放进统一错误处理规则。
6. 找到了两条不同回执,也不能挑一条看起来顺眼的
第三个样本叫 run-conflict-0917。
它给同一次尝试提供了两条互相冲突、尚未确认的关联候选,工具返回 mappingState=AMBIGUOUS,保留候选查询标识和回执引用,但不继续返回某一个候选的应用状态。
这里的"冲突"是样本中明确标记的关联问题。不能反过来说,只要查询关联到多个应用就是冲突。一次查询合法地包含多个执行阶段,与两条互相排斥的回执,是两种不同的情况。
把两种情况区分开,返回结构可以保持简单:
| 情况 | 工具怎么做 | 助手怎么说 |
|---|---|---|
| 有可靠回执 | LINKED,保留完整关联列表 | 按已确认链路继续查 |
| 没有可靠关联 | MISSING,不补造标识 | 明确当前无法追到引擎 |
| 候选互相冲突 | AMBIGUOUS,保留候选与引用 | 先核对回执,暂停针对单个应用下结论 |
真正影响诊断质量的,不是"关联率看起来有多高",而是错误关联有没有被悄悄当成确定事实。一条关联错了,后面的指标和日志越丰富,结论反而可能越自信地跑偏。
7. 用三个样本检查链路,而不是只演示成功分支
在已启动的教学服务上,可以通过配套客户端调用:
bash
python3 scripts/mcp_client.py ops_trace_engine '{"runId":"run-current-0917","attemptId":"a1"}'
客户端从环境变量读取教学令牌;完整启动步骤见 lab-07-12/README.md。如果只想自动复现,用 python3 scripts/verify_mcp.py 即可临时启动服务并检查结果。
至少检查以下三组:
| runId | 预期结果 | 必须避免的错误 |
|---|---|---|
| run-current-0917 | LINKED,关联到 demo-app-20 | 把调度 RUNNING 当成引擎正在计算 |
| run-unlinked-0917 | MISSING,应用列表为空 | 用相似任务名猜一个应用 |
| run-conflict-0917 | AMBIGUOUS,保留候选 | 自动选择第一个候选继续诊断 |
再补一条反例:把 attemptId 改成这次运行中不存在的 a3。工具必须拒绝,不能去别的运行里寻找同名尝试。
对主样本,预期回答可以这样写:
已通过本次尝试的模拟提交回执,将
run-current-0917/a1关联到demo-query-20和demo-app-20。调度侧保持 RUNNING,但引擎最近一次在 09:39 上报 QUEUED,队列运行数为 8/8,原因是 QUEUE_RUNNING_LIMIT。当前应优先核对队列准入与释放事件;尚不能说明整个等待区间都由同一限制导致。
这依然是预期回答模板,不是某个模型已经通过的验收成绩。
8. 有了准确标识,下一步才值得查日志
小周现在知道,排查链路不是几次模糊搜索,而是一组有来源的关联。
对 run-current-0917/a1,下一步可以查看提交前后发生了什么、轮询有没有异常、队列状态有没有变化。对关联缺失的样本,也可以限定到同一次尝试查提交日志。
下一篇我们就给助手一个受约束的日志查询工具:既能找到异常,也保留前后文;查到一半、采集有缺口或单条消息被截断,都要如实告诉读者。