zizi d97e194383 docs(治理): SoT注册表+docs-gate六检门,清缩历史档126→69
- 注册表:docs/architecture/README.md §2,37 份 canonical(frontmatter topic+canonical:true)与表双向机器对账
- 门:.agents/tools/docs-gate.py 六检(品牌根/canonical唯一/死链/入口卫生/留痕隔离/设计档申报),挂 .githooks/pre-commit(本仓已激活)+ .gitea/workflows/docs-gate.yml(待runner)+ wave-close 第8步;旧 check-deadlinks.sh 退役并入 G3
- 清缩:删 54 项历史档(plans 16/agent-specs 设计与spike 20/brainstorms 5/memorys 3/goals+作战清单完成史归档/王蓝莓design/SAA现状html/add-game-template);channel-spike 564K 代码资产迁仓级 spikes/;六件删前蒸馏已迁(代码评审16条open项→进度总账§5、prefix-cache字段表→cheap-model skill、意图基线29条→需求清单附录、九门降级rationale→验收门、A11 TODO→tech-decisions、3layer边界→littlejs-game-dev 指针)
- 修口径约90处:六处SAA『现行主线』旧标、Nacos/RocketMQ『未部署』旧述(07-01反转)、gameDefinition残留、全部死链改 git show 定位;AGENTS.md 249→136行(决策史归tech-decisions);_index 改纯在飞板;对外演示版 md→html
- 依据:docs/agent-specs/2026-07-02-文档治理-{全量普查与裁决-report,SoT注册表与治理门-设计}.md(四路普查191份md+Codex/Opus双评审必修项已折入);恢复基线 8ea97234(单档 git checkout 8ea97234 -- <路径>)
2026-07-02 14:24:12 +08:00

297 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
topic: 观测体系
canonical: true
date: 2026-06-21
---
# 运维域 · 线上观测体系设计
> 绘境AI 线上观测体系的 canonical 设计稿,回答线上服务跑起来之后靠什么持续看见它、靠什么在它出问题时被叫醒。它补的是运维主档 [README §4](README.md) 立位、但一直留白的那一块:主档 §1.3/§1.4 回答了"部署后跑一遍冒烟门确认健康"和"健康到 ≥99.5% 可用性才算达标",但"线上持续靠什么观测、5xx 飙升靠什么发现、生成失败率掉了谁告诉你"这一问,现行只有一个秒级冒烟门加一个可用性数字撑着,配不上一个要对外服务的平台。
> **给谁看**:搭观测栈和埋点的工程师、值守线上的人、做生成质量与成本对账的人、关心可用性达标证据的创始人。
> **边界**:本稿只管观测本身——采集怎么埋、看板告警怎么分工、admin 怎么进大盘。观测栈的部署落点与 k8s 迁移强相关:创始人 2026-06-21 已定本阶段上 k8s、观测栈随服务一起往 k8s 收(见 [k8s迁移.md](k8s迁移.md))。本稿按那份迁移档的目标形态设计——观测栈落在 mini-desktop 单节点 k3s 的 `observability` namespace;k3s 就绪前可先以 mini-desktop 裸 docker-compose 过渡。埋点产出标准 OTLP,与后端组件的部署方式在 OTLP 协议这个接口上解耦,所以"先 docker-compose 过渡、后随 k8s 收编"对应用侧埋点零改动。k8s 怎么迁、迁到什么形态属 k8s迁移.md,本稿不展开。
---
## 1. 现状:有一半客户端地基,但大半没接线、没后端
后端的观测地基处于"零件在、没装上"的状态,逐件核对如下,把"现在已经有什么"和"还得新建什么"分清楚,免得把蓝图当现状。
yudao 框架层有一个 `huijing-spring-boot-starter-monitor` starter,它里面的零件状态参差不齐:
- `TraceFilter`:**已实现、能用**。每个响应回写一个 `trace-id` 头(`TraceFilter.java`,header 名就是小写 `trace-id`,不是 `X-Trace-Id`)。但它取的 traceId 来自 SkyWalking 的 `TracerUtils.getTraceId()``TraceContext.traceId()`;SkyWalking agent 不在席时,这个值是占位串而非真实 traceId。这条是"trace_id 从入口注入"那条规范的落脚点,但要真出一个能串链路的 traceId,得先把它对齐到下面要新建的 OTel trace context。
- `@BizTrace` 注解 + `BizTraceAspect` 切面:**注解和切面类都在,但 AOP 当前被禁用**。`HuijingTracerAutoConfiguration` 里注册切面的 `@Bean`(`bizTracingAop()` / `tracer()`)是注释掉的(源码 line 2840,带 `TODO 后续换 opentelemetry` 的注释),所以 `@BizTrace` 现在贴上去不会织入任何埋点。要用得先取消注释并验证 AOP 织入——但既然要换 OTel,更可能是直接用 OTel 的手动 span 取代它,而不是去启用这套绑 SkyWalking 的旧切面。
- `HuijingMetricsAutoConfiguration`:**类在,给指标打 `application=<服务名>` 公共标签**;`micrometer-registry-prometheus` 作为 `optional` 依赖带进了 starter。
- SkyWalking `apm-toolkit-*`(含 logback 集成)和 `spring-boot-admin-starter-client`:同样以 `optional` 依赖躺着,默认不启用。
关键的一条事实:**这个 monitor starter 没有进生成主线的部署单元**。线上跑的单体是 `huijing-server`(`HuijingServerApplication` 聚合 `game-module-aigc-server` 等十几个业务模块),它的 pom 里没有 `huijing-spring-boot-starter-monitor`(该 starter 只被 gateway/infra/pay/system/bpm 等模块引了)。所以 `TraceFilter`/`@BizTrace`/`HuijingMetricsAutoConfiguration` 这套地基,**对 aigc 这条生成主链路的运行进程而言,classpath 上根本没有**——要用,得先在 `huijing-server`(或 aigc-server)的 pom 里引入。
生成主线那一侧倒是真有一段已落地的观测信号:aigc-server 已经引入 `spring-ai-alibaba-graph-core` + `spring-ai-alibaba-starter-graph-observation`(SAA v1.1.2.2,见 aigc-server pom),SAA 裸图编排在 `ObservationRegistry` 非空时挂上 graph-core 自带的 `GraphObservationLifecycleListener`(生产代码 `SaaStudioGraph.build` 已接,`AigcExecutorConfiguration` 软取 Micrometer `ObservationRegistry`,缺则降级 NOOP),**每个图节点发一条 `spring.ai.alibaba.graph.node.<id>` 的 Micrometer observation,带 trace 和耗时,失败反映在指标里**(见 [`.agents/skills/saa-graph-orchestration.md`](../../../.agents/skills/saa-graph-orchestration.md) §5)。这是后端唯一一段已经在以 Micrometer 标准产出、且依赖已就位的观测信号——缺的只是一个把它收走的后端,以及一个非 NOOP 的 `ObservationRegistry`(后者要 Spring Boot actuator/micrometer 装配在席才有,而 actuator 也还没引)。**口径**:这段 SAA 节点 observation 是**已落地的遗留信号**——生成框架已于 2026-06-25 收敛到 AgentScope、SAA 降为最低优先级,所以它是现存 SAA 实现的观测地基,先按现状接进来、待生成主线迁到 AgentScope 后再按新框架重挂;观测栈与生成框架经 OTLP 在协议层解耦,这段信号的接入不受框架收敛影响,下文凡说"SAA 节点 observation"均按此理解。
前端那侧是另一种现状:studio 已经有业务遥测(性能 / 游玩 / 会话),事件经 `/app-api` 落进 `game_telemetry_event`,聚合成 `game_telemetry_game_stat`,再回灌游戏热度和质量分(见 [后端数据模型 §2.3](../后端/数据模型.md))。这条业务数据回路和"前端页面在真实用户浏览器里加载多慢、报了什么 JS 错"这种前端可观测性是两回事,后者现在完全没有。
缺口归纳成三条:
1. **没有任何遥测后端在收数据**。Micrometer 指标没人 scrape,trace 没地方落,日志还是各进程自己的文件。蓝图里画过的 Prometheus / Grafana / Jaeger 三个容器,MVP 现实从未起过(历史回收判定 运维基建-001)。
2. **采集标准不统一,且和创始人选定的不一致**。现状脚手架是 SkyWalking + Micrometer 两套各走各的;创始人 2026-06-21 定的是 **OpenTelemetry(OTel)一套统一 trace / metrics / log**。这不是补一个后端就完,而是要把采集标准收敛到 OTel。
3. **告警是一张纯文档表,没有一根通道真接上**`security-and-reliability.md` §6 把 P0P3 升级链和触发条件写全了(P1=5xx>2% 或生成成功率<70%),但飞书/钉钉 webhook 谁配P0 电话怎么打,全是空头承诺(历史回收判定 运维基建-004)。预算里留了 ¥500/月给监控/备份,但这笔钱对应的能力一直是空的(运维基建-027)。
这份设计要做的,就是把这三条补成真东西:引入 OTel 统一采集把已有的 SAA 图节点 observation 和后续新埋的指标接进去 Grafana + 夜莺喂上数据把告警通道接通并在 admin 开一个进大盘的入口下面 §3 凡涉及新建的契约面(后端 pom 依赖新增指标埋点admin 路由与查询接口),会单列 contract-first 清单,讲清"现在没有要新建什么"。
---
## 2. 设计目标
观测体系做成之后,要能回答四个层次的问题,且每一层都有人(或告警)在看:
- **基础设施层**:每台机器每个进程的 CPU / 内存 / 磁盘 / GC,够不够会不会 OOM磁盘会不会涨满
- **服务层**:后端各 API QPS / 错误率 / P95 延迟,可用性够不够 99.5%,达标用 error budget 说话而不是拍脑袋
- **业务层**:生成成功率(≥80% 是验收线,也是最关键的业务健康指标)、生成 P50/P95 耗时队列积压广告/分账链路有没有断生成成本有没有失控
- **前端层**:真实用户那边首屏多慢(P75<3s 目标)、JS 报错率关键交互(刷流点生成)的成功率
配套的硬目标:
- **采集统一**:trace / metrics / log 三件都经 OTel 一套标准产出,trace_id 从网关入口贯穿到 SAA 节点和 new-api 调用,一个 trace_id 能把"一句话生成游戏"这条链从前端点击串到模型返回
- **关键告警必达**:可用性错误率生成失败率三类告警必须真能把人叫醒(不是只在看板上变红)。
- **admin 一键进盘**:运营和排查从后台点一下就进监控大盘,不用各记各的 Grafana 地址各自登录
- **埋点与部署解耦**:观测栈按 k8s迁移.md 落在 mini-desktop k3s observability namespace,k3s 就绪前先 docker-compose 过渡( §4)。无论过渡形态还是 k3s 形态,后端那侧的埋点(OTel agent + 语义约定)一行不用改,只换观测组件的部署方式——埋点是应用内的事,观测后端是部署的事,两者在 OTLP 协议这个接口上对接
---
## 3. 方案
### 3.1 整体形态:OTel 采集 + 三类后端 + Grafana/夜莺双看板
观测体系是一条"采集 管道 存储 看板/告警"的链创始人点名的组件是 OTel(采集和管道)、Prometheus(存指标)、Grafana 和夜莺(Nightingale,在最上面分工做看板与告警);trace 后端和日志后端创始人没点具体哪个,本稿给倾向并标"选型待定"( §6)。整条链的形态如下
```mermaid
flowchart TB
subgraph SRC["采集侧(被观测的进程)"]
BE["huijing 后端单体<br/>OTel Java Agent 自动注入<br/>+ Micrometer 指标 + SAA 节点 observation"]
FE["game-studio / game-admin 前端<br/>OTel Web SDK(可选,后期)<br/>页面性能 / JS 错误 / 前端 trace"]
HOST["主机/中间件<br/>node-exporter · MySQL/Redis exporter"]
end
subgraph PIPE["管道:OTel Collector(统一入口)"]
COL["OTel Collector<br/>OTLP 收 trace/metrics/log<br/>批处理 / 脱敏 / 路由"]
end
subgraph STORE["存储后端(各管一类)"]
PROM["Prometheus<br/>指标时序"]
TRC["trace 后端<br/>Tempo / Jaeger(选型待定)"]
LOG["日志后端<br/>Loki 等(选型待定)"]
end
subgraph VIEW["看板与告警"]
GRAF["Grafana<br/>统一看板(trace/metrics/log 三联查)"]
N9E["夜莺 Nightingale<br/>告警规则引擎 + 通知分发<br/>P0P3 升级链落地"]
end
ADMIN["game-admin 控制台<br/>观测入口 / 观测大图"]
BE -->|OTLP| COL
FE -->|OTLP/HTTP| COL
HOST -->|scrape| PROM
COL -->|metrics| PROM
COL -->|trace| TRC
COL -->|log| LOG
PROM --> GRAF
TRC --> GRAF
LOG --> GRAF
PROM -->|查询/拉取| N9E
N9E -->|webhook| NOTIFY["飞书 / 钉钉 / 短信 / 电话"]
ADMIN -->|嵌入 + SSO 免登| GRAF
ADMIN -.->|告警概览 API| N9E
```
几个选型决定要讲清为什么:
**为什么 OTel Collector 居中,而不是各组件直连后端。** 让每个被观测进程直接把数据推给各自的后端(指标推 Prometheustrace Jaeger),会把后端地址协议脱敏逻辑全硬编码进每个应用, k8s 或换后端就得改一圈中间放一个 OTel Collector 当统一入口,应用只认一个 OTLP 端点,**脱敏批处理采样路由全在 Collector 这一层做**——手机号/token 脱敏这条规范(`security-and-reliability.md` §6)就落在 Collector processor 里集中执行,而不是指望每个埋点点各自记得脱敏换后端 k8s , Collector 的导出配置即可,应用零改动这正是"先单体可跑后平滑上 k8s"能成立的技术支点
**为什么后端用 OTel Java Agent,而不是继续用 SkyWalking toolkit。** 现状脚手架里那套 SkyWalking `apm-toolkit-*` yudao 自带的,但它和 Micrometer 是两条平行的采集路,且不是创始人选定的标准OTel Java Agent **零代码自动织入** Spring MVC / JDBC / Redis / HTTP client span,同时通过 OTel Micrometer bridge 把现有的 Micrometer 指标(含已经在产出的 `spring.ai.alibaba.graph.node.*` 那批 SAA 节点 observation)一并收走所以采用 OTel Agent = 自动 trace + 收编现有指标,**一步把两条平行路收敛成一条**;SkyWalking 那套 toolkit 退役为不启用(依赖留着不删,免得动 yudao 框架层, profile 里不开)。这是这份设计里唯一一个"用框架现成的 vs 换标准"的实质取舍,理由是创始人定了 OTel, OTel 能向后兼容地把 Micrometer 那部分一起带走,换的成本低收益是采集口径统一
**为什么是 Grafana 和夜莺两套,各管什么。** 这两个不是冗余,是分工:
- **Grafana = 看(read-only 的眼睛)**。它是统一看板层, Prometheus 的指标trace 后端的链路日志后端的日志接成数据源,做成大盘,支持"从一条慢请求的指标点进它的 trace再点进这段时间的日志"这种三联下钻admin 一键进的"监控大盘"就是 Grafana
- **夜莺 Nightingale = 叫(会响的告警)**。夜莺是国产的告警规则引擎加值班/通知分发系统,中文生态、飞书钉钉接入顺手,正好补上"告警通道一根没接"这个最大的空。它从 Prometheus 拉指标算告警规则, `security-and-reliability.md` §6 那张 P0P3 升级链表**变成真的规则和真的通知**(P0 电话P1 短信+P2/P3 )。
简言之 Grafana 负责"你主动去看时看得清",夜莺负责"你没在看时它把你叫来"。两者都从同一份 Prometheus 指标取数,口径一致。(注:Grafana 自带 alerting,夜莺也能做看板,功能上有重叠;这里按创始人选定的分工——Grafana 主看板夜莺主告警——各取所长,不强行二选一。)
### 3.2 后端怎么埋:Agent 兜底 + 关键链路手动加 span
后端埋点分两层,自动的兜底广度手动的补关键链路的深度
**自动层 = OTel Java Agent。** 启动时挂一个 `-javaagent:opentelemetry-javaagent.jar`,配好 OTLP 端点和服务名,它自动给每个 HTTP 入口每次 JDBC 查询每次 Redis 调用每次出网 HTTP(含调 new-api 的那次)生成 span 并串成 trace这一层不改一行业务代码,就把"一个请求经过了哪些 SQL哪些下游各花多久"全画出来
这里有一个必须解决的双 traceId 问题现有的 `TraceFilter` 回写的响应头是 `trace-id`(小写),它的值来自 SkyWalking `TracerUtils.getTraceId()`; OTel agent 走的是 W3C `traceparent` 标准生成自己的 traceId如果两套并存而不打通,响应头回的 `trace-id` Grafana OTel trace traceId 会是两个不同的值,前端拿着 `trace-id` Grafana 查不到对应链路统一成一根的方向是:启用 OTel agent , `TraceFilter` 回写的来源从 SkyWalking `TracerUtils` 换成 OTel 当前 span traceId(`Span.current().getSpanContext().getTraceId()`),让响应头里的 `trace-id` 就是 OTel traceId这样前端报错时带回的 trace_id 能直接在 Grafana 里查到对应后端链路具体改 `TraceFilter` 取数来源还是用 OTel baggage/响应头注入扩展,**待核**( §6 第一条)。
**手动层 = 给生成链路补结构化 span 和业务指标。** 自动 agent 看得见"调了 new-api 花了 8 ",但看不见"这是生成的第几步过了几道门就绪分多少"——这些业务语义得手动埋其中 SAA 图节点这一段地基已经在:每个节点已发 `spring.ai.alibaba.graph.node.<id>` observation(依赖已就位, §1),只要接上非 NOOP ObservationRegistry,这些自动变成 trace 里的 span但下面这几个**关键业务指标目前在代码里全部零命中,是要新建的埋点,不是已有数据**——它们需要在对应的 Worker / 控制平面类里补 `MeterRegistry`( OTel `Meter`)的计数/计时注册调用后才会有数据各指标的设计口径(都打成 Micrometer/OTel metrics, `template``model``source` 维度标签):
- `gen_task_total{status}` —— 生成任务总数按终态分(对齐 `game_aigc_task.status`:2 成功 / 3 失败 / 4 超时 / 5 取消),**生成成功率 = success / (success+fail+timeout)**,作为 P1 告警(<70% 触发)和验收线(≥80%)的取数源埋点位:任务终态回填处(`AigcTaskServiceImpl` / Worker 写终态那一步)。
- `gen_duration_seconds` —— 生成耗时分布(histogram), P50/P95,对齐"P50<60sP95<180s"的硬指标埋点位:Worker 一局生成的起止
- `gen_queue_depth` —— 队列积压(全局在飞 = queued+running)。**取数口径要对齐控制平面的真实实现**:背压阈值是 `AigcControlPlaneProperties.queueDepthLimit`,代码里当前是占位值 **50**(注释明确"占位值,需确认",不是蓝图里画过的 500);超阈值时走业务码 `AIGC_BACKPRESSURE_REJECTED`(`1_101_001_002`),**HTTP 200body.code 0,不产生 HTTP 429/503**( [开闸验收门-W-G1](../架构/生成引擎/验收门.md) 决策E,以及 `ErrorCodeConstants` 注释)。所以这个指标的告警语义是"在飞数逼近 `queueDepthLimit`",不是"返回 429 的次数"。埋点位:控制平面 `enqueueWithControlPlane` 背压判定处
- `gen_gate_fail_total{gate}` —— 九门各门失败计数, `game_aigc_task.trace_json`(九门轨迹账本)那条信息里出,定位是哪道门在拖低成功率埋点位:九门校验节点
- `llm_cost_cents` / `llm_cache_hit_rate` —— 生成成本与前缀缓存命中率。**成本数据源待薄片落地**:后端代码现在没有任何读取 new-api 计费日志(`logs.quota`)的逻辑——`logs.quota 权威对账`目前只是 `SaaGraphDispatcher` 里的一句注释和 memorys 死库(已删,git 可查) 的一个待建薄片设想,不是已接通的链路要把成本做成实时指标,得先落地一个读 new-api `logs.quota` 的薄片把成本拉出来,再注册成指标。**缓存命中率掉到 50% 以下要告警**(历史回收判定 运维基建-003 的成本早期哨兵,缓存因 few-shot 字节漂移失效会让成本翻几倍)。
> **contract-first(新建项清单)**:生成链路要接观测,跨模块面的新增物有三类——① **后端依赖**:`huijing-server`(或 aigc-server)pom 引入 OTel 相关依赖(actuator + micrometer + OTel Java Agent 走启动参数,不进 pom 也可),让 `ObservationRegistry` 非 NOOP;② **指标埋点**:上面五个指标在对应 Worker/控制平面/九门节点类补 `MeterRegistry` 注册调用(纯应用内新增,不动契约 yaml/DB);③ **成本薄片**:新建读 `logs.quota` 的成本采集薄片(单独一件事,见上)。这三类都是"现在没有、要新建",落地前观测大盘上的生成指标全是空的。
接好之后,生成这条最值钱的链路既有 trace(一次生成的完整时间轴 + 每个节点)、又有 metrics(成功率/耗时/积压/成本的趋势与告警),trace metrics 经同一个 trace_id 关联下面这张图把"一句话生成游戏" trace 贯穿画出来:
```mermaid
flowchart LR
U["前端点击<br/>生成游戏"] -->|traceparent 注入| GW["网关入口<br/>TraceFilter 起 root span"]
GW --> AIGC["aigc 服务<br/>建 game_aigc_task"]
AIGC --> SAA["SAA 裸图编排<br/>每节点一条 observation span"]
SAA -->|node: brief| N1["..."]
SAA -->|node: gen_source| N2["调 new-api<br/>OTel agent 自动 span"]
N2 --> NAPI["new-api 网关 → 便宜 LLM"]
SAA -->|node: nine_gates| N3["九门校验<br/>gen_gate_fail 指标"]
SAA --> BUILD["build-from-source<br/>编译产物"]
AIGC -. "终态回填 status<br/>gen_task_total{status}" .-> METRIC[("指标:成功率/耗时/成本")]
style METRIC fill:#1a2b3c,color:#fff
```
一个落地约束要写在这里:**埋点开关默认关 profile 显式打开,失败绝不影响主流程**。OTel agent 和指标上报都不能因为 Collector 挂了就拖垮生成——agent 走异步批量上报Collector 不可达时本地丢弃(对齐 SDK 降级铁律里 Telemetry "上报失败静默丢弃稳定性>数据完整性"的精神,见 `security-and-reliability.md` §7)。这条保证了观测栈本身是旁路,挂了不咬主链路。
### 3.3 前端怎么埋:先收 JS 错误与首屏,trace 打通靠 traceparent
前端可观测性分两步走,因为它和已有的业务遥测要划清边界,别重复造。
业务遥测(谁玩了哪款、留存多少)继续走现有的 `/app-api/telemetry``game_telemetry_event` 那条,不动。**新增的是"前端运行健康"这一类**:页面首屏耗时(对齐 P75<3s)、JS 运行时报错关键接口(刷流生成发布)的失败率这部分用 OTel Web SDK 采集, OTLP/HTTP 推给同一个 Collector
更要紧的是把前后端 trace **连成一根**:前端发起后端请求时,在请求头注入 OTel `traceparent`(W3C trace context 标准头),后端 agent 认这个头把后端 span 挂到同一个 trace 这样一次"点生成 后端建任务 SAA 跑图 调模型"的全过程是一个 trace,从用户浏览器一直串到模型返回生成失败时,前端拿到的 trace_id 能在 Grafana 里直接查到这次生成卡在哪个节点哪道门调模型报了什么——这是排障效率的关键
前端这块**排在后端之后做**:后端 + 基础设施的观测是 99.5% 可用性达标的直接证据,优先级最高;前端运行健康是体验优化,价值真实但不卡可用性目标,放第二批( §4)。
### 3.4 告警:把文档表变成夜莺的规则和通道
告警是这份设计里"从无到有"最实的一块`security-and-reliability.md` §6 那张 P0P3 表现在是纯文档,夜莺要把它落成三样东西:**规则(什么条件触发)、分级(P0/P1/P2/P3)、通道(怎么通知多久响应)**。逐级对应:
| 级别 | 触发条件(夜莺规则,取数自 Prometheus) | 通道 | 响应时限 |
|---|---|---|---|
| **P0** | 服务不可用(health 探测连续失败 / 整机宕)、数据丢失安全事件 | 电话 + 短信 + | 5 分钟 |
| **P1** | `5xx 占比 > 2%` 持续 N 分钟 / **生成成功率 < 70%** / 支付异常 | 短信 + | 15 分钟 |
| **P2** | `API P95 > 800ms` / 在飞数逼近 `queueDepthLimit`(当前占位 50)/ 错误率上升 / **缓存命中率 < 50%** | 群通知 | 1 小时 |
| **P3** | 非核心模块降级 / 日志异常增长 / 磁盘水位 > 80% | 群通知 | 工作时间 |
三类创始人点名的关键告警都在表里有家:**可用性**走 P0(health 连续失败)、**错误率**走 P1(5xx>2%)、**生成失败率**走 P1(成功率<70%)。另外把两条历史回收清单里悬空的哨兵补进来:**缓存命中率<50%**(成本早期哨兵,运维基建-003) P2,**磁盘水位>80%**(运维基建-021,mini-desktop 上 staging+构建产物+jar 备份+批跑证据会涨满磁盘、满了 MySQL 直接挂)进 P3。
通道落地有一条务实的分期:**MVP 阶段先接飞书/钉钉群机器人 webhook 把 P0P3 全打通**(这是零成本、当天能接的),P0 的"电话+短信"依赖短信网关/电话服务,**随真实化的支付/短信能力一起接**(现在 P0 先用群里 @所有人 + 短信兜底,电话留接口)。这条避免了"因为电话通道没接就说告警没做"——先把群通知接通,贵的通道按需补。
webhook 密钥(飞书/钉钉群机器人的 webhook URL 与签名 secret)按内网阶段铁律落进 [`docs/内网凭据与端点.md`](../../内网凭据与端点.md),和 `NEWAPI_KEY` 同处,**不走环境变量**——内网阶段地址和密钥统一进项目文档,夜莺配告警通道时从那份凭据档取。
### 3.5 admin 观测入口:从无到有的一整套(现状是零)
先讲现状:**这一块现在完全是空的,要整套新建**。`game-admin/src` 下没有任何观测/监控相关的路由、菜单或 Vue 组件(现有的 `infra/redis monitor` 是 yudao 自带的 Redis 监控,与本设计无关),后端也没有任何给 admin 用的观测查询接口。所以"控制台一键进监控大盘"不是接一根线,而是要补齐前端、后端、嵌入安全三件,缺一不可。
创始人要的入口落在 game-admin 上(Vue3 + Element Plus),做法是在控制台新建一个"观测"菜单,底下两块:
- **观测大图**:一个嵌入页,把 Grafana 的核心大盘(可用性 / 错误率 / 生成成功率 / 关键链路)以 iframe 或 Grafana 的 embed 方式嵌进 admin,运营点一下就看见全局健康,不用记 Grafana 地址。**注意 iframe 嵌入不是写个 `<iframe src>` 就完**:Grafana 默认带 `X-Frame-Options: deny`、且有自身的 CSP,浏览器会直接拒绝被 admin 页面嵌入。要让嵌入成立,得在 `grafana.ini` 里开 `[security] allow_embedding = true`(并按需放开 `cookie_samesite`、配 CSP `frame-ancestors` 允许 admin 源)。如果不想放开 Grafana 的嵌入限制,另一条路是不嵌 iframe、改由 admin 后端的观测查询接口(见下)直接取指标数据、admin 前端自渲核心曲线——避免跨源嵌入的安全开关,但要自己画图。两条选哪条见 §6。
- **跳转入口**:对需要深挖的场景(下钻某个 trace、查某段日志),提供直接跳转到 Grafana 对应面板的链接,在 Grafana 里做三联下钻。
这套入口要新建的三件,逐件列清(都是现在没有、要新建):
1. **后端管理员专属查询接口**:在 admin 侧后端(走 `system_users` 鉴权的 admin-api 段)加观测查询接口,带 yudao 的权限注解(`@PreAuthorize("@ss.hasPermission('observ:dashboard:query')")` 之类),用途有二——给"API 直渲"方案供数(读 Prometheus/夜莺接口聚合后回前端),以及给 SSO auth-proxy 做这层可信代理的落点。**接口受权限守门,不能让未授权 admin 用户读到观测数据,更不能把 Grafana/Prometheus 裸暴露出去**。
2. **admin 前端菜单 + 路由 + 组件**:在 admin 加"观测"一级菜单、对应路由,和观测大图/跳转入口两个视图组件。这部分前端 file 级细节(具体路由路径、组件文件、菜单挂载点)留在 admin 活代码里落,本稿只定形态。
3. **嵌入安全处理**:即上面 iframe 方案要处理的 `X-Frame-Options`/`allow_embedding`/CSP,或选 API 直渲彻底绕开嵌入。这件和第 1 件的"接口受权限守门"一起,构成观测入口的安全边界。
这里有个必须解决的体验问题:**别让运营进个监控还要再登一次 Grafana**。admin 用户走的是 yudao 原生的 `system_users` 身份(和 C 端 `game_player` 是两套人,见 [后端数据模型 §4](../后端/数据模型.md))。所以 admin → Grafana 要做**单点免登**。可选两条路,**具体选哪条待核**(见 §6):
1. **反向代理 + 请求头透传**:admin 后端做一层代理,把 admin 已登录用户的身份经可信请求头(Grafana 的 auth proxy 模式)透给 Grafana,Grafana 信任这个头直接放行。好处是 admin 和 Grafana 共用一套登录态,运营无感。
2. **Grafana 独立账号 + 嵌入只读**:Grafana 开匿名只读或共享只读账号,admin 嵌入的大盘走只读视图,深挖才跳独立登录。简单但体验差一截。
倾向第 1 条(auth proxy 透传),它最贴合"一键进盘、无二次登录"的要求;但要核 Grafana auth proxy 在嵌入(iframe)场景下的 cookie/同源限制,以及 admin 后端做这层代理的安全边界(别把 Grafana 整个暴露出去)。下面这张图画 admin 进盘的链路:
```mermaid
flowchart LR
OPS["运营<br/>(system_users 已登录 admin)"] --> ADMIN["game-admin 控制台<br/>观测菜单"]
ADMIN -->|观测大图:嵌入只读大盘| EMBED["Grafana 嵌入面板<br/>(可用性/错误率/生成成功率)"]
ADMIN -->|深挖跳转| PROXY["admin 后端 auth-proxy<br/>透传 system_users 身份头"]
PROXY -->|可信头免登| GRAF["Grafana 完整大盘<br/>trace/metrics/log 三联下钻"]
ADMIN -.->|告警概览| N9E["夜莺告警列表<br/>(最近告警/值班状态)"]
EMBED --> GRAF
```
可以参考 yudao-cloud / vue-pro 已有的监控集成——它本身在"基础设施/监控中心"菜单下集成过 Spring Boot Admin、SkyWalking、Grafana 这类外部页面的嵌入,admin 侧的菜单挂载和嵌入方式可以直接借鉴(yudao 的具体集成形态**待核**,见 §6)。
### 3.6 与现有冒烟门、可用性目标怎么衔接
观测体系不是另起炉灶,它要和现有的两个东西接上,各管一段、不重叠。
**和冒烟门的关系 = 点检 vs 常态监控,互补。** `deploy/smoke-test.sh` 是**部署那一刻的就绪点检**(秒级、一次性、确认这次部署的关键 API 在岗),观测体系是**部署之后的常态监控**(7×24 持续、看趋势、出问题告警)。两者职责不重叠:冒烟门回答"这次部署有没有部坏",观测回答"部好之后跑得住不住"。衔接点在于——冒烟门可以把它那 12 项的结果也打成一条指标推给 Prometheus(部署成功/失败、各项耗时),这样"每次部署的健康度"也进了可观测的历史,而不是只在部署当时的终端里闪一下。
**和可用性目标的关系 = 观测是 ≥99.5% 的度量与举证手段。** 现在 ≥99.5% 是个目标数字,但没有任何东西在持续度量它、没有 error budget 在被消耗和记录。观测体系把它落地:用 health 探测的成功率算游戏流 API 的实际可用性,对着 `security-and-reliability.md` §5.1 那张 SLO 表(游戏流 99.5%/月 error budget 3.6 小时、AI 生成 99%/7.2 小时、支付 99.9%/43 分钟)做 burn rate 看板和告警。这样"达没达标"从一句承诺变成一张有数据、有 error budget 余量的看板,创始人随时能看见这个月还剩多少容错预算。
### 3.7 LLM 网关(new-api)健康巡检:盯生成命脉本身,而不是盯调它的那次请求(创始人 2026-06-22 历史回收判定捡回)
上面 §3.2 埋的 `gen_task_total`/`gen_duration_seconds` 这套指标,盯的是"业务侧调 new-api 这次调用快不快、成没成";它们看不见的是网关本身的健康——new-api 背后挂着多条上游通道,每条对应不同供应商的 key,一条通道的 key 失效或被供应商封了,落到业务侧只表现为"生成成功率掉了",但那已经是结果坏了之后。把网关自身的健康单拎出来盯,是因为它是整个平台的命脉:网关一旦多条通道批量失效,所有生成全停,而不是某一类游戏生成不了。这不是假想的风险——2026-06 真发生过二厂系通道整批作废,当时只能靠人去翻日志才发现生成为什么集体失败。
要盯的是三件,它们和生成成功率告警是互补的前后两层。**第一件是多通道健康巡检**:周期性地对 new-api 配置的每条上游通道做一次最小探活(一个极小的 chat completion 请求,只问通道还通不通、不在意内容),把每条通道的"通/不通、延迟"打成指标,这样看板上能直接看见"现在还有几条通道活着",而不是等业务调用失败了反推。**第二件是 key 激活状态监控**:盯每个 key 的有效性与额度,new-api admin 接口能查出 key 的启用状态和已用额度,key 被禁用或额度耗尽时要在它影响生成之前就告警。**第三件是批跑冻结阀**:当健康巡检判定可用通道掉到某个下限(比如只剩一条、或全废),要能自动把后台批量生成(W-G1 竞标批跑、夜间批生成这类高并发消耗 key 的任务)冻结住,避免在通道已经半废的情况下继续猛打、把仅存的通道也打爆,同时给前台单次生成留一条降级到单通道运行的活路。冻结阀拉起后告警必达,由人确认通道恢复后再解冻。
这三件的告警分层接进 §3.4 夜莺那张表:**单条通道失效、key 额度告警**属于"结果还没坏、但前哨亮了"的前置预警(归 P2 一档,先在群里通知去补通道/换 key);**可用通道全废、批跑冻结阀拉起**属于命脉级,生成即将或已经全停(归 P1,等同生成成功率<70% 的紧急级,短信+群叫人)。次序很清楚:通道健康巡检在前(结果坏之前的前哨),生成成功率<70% 告警在后(结果已经坏了),两层都要,缺前哨就只能等生成集体失败了才知道网关出了事
> **巡检的部署落点**:网关健康巡检是一个轻量周期任务(每条通道探活 + 查 key 状态),它的运维归属并入下面 README §5 讲的"平台后台 job 调度面"——和结算 job、telemetry 聚合 job 一样,作为一个定时 job 跑在 staging 机上,失败/冻结阀触发的告警接进夜莺。探活产出的通道健康、key 额度指标推给 Prometheus,在 Grafana 上和生成成功率画在同一块大盘,让"网关背后还活着几条通道"和"生成成功率"并排可见。
> **contract-first(待落地,现在没有)**:① **巡检 job**:新建一个 new-api 网关健康巡检 job(周期探活每条通道 + 查 key 激活/额度),在后台 job 调度面注册;它读 new-api 的 channel/key 管理接口(端点与 admin token 按内网铁律取自 [`docs/内网凭据与端点.md`](../../内网凭据与端点.md))。② **新增指标**:`llm_channel_up{channel}`(每条通道通/不通)、`llm_channel_latency_ms{channel}`(探活延迟)、`llm_key_quota_remaining{key}`(key 剩余额度)——三者都是纯应用内新增的 Micrometer/OTel 指标,不动契约 yaml/DB。③ **冻结阀开关**:批跑侧需要一个可被巡检 job 置位的"生成批跑冻结"标志(一个 feature-flag 形态的开关,通道全废时置位、人工确认恢复后清),批跑任务启动前先读它;具体落成配置项还是控制平面状态待落地时定。这三件落地前,网关健康在看板上是空的,通道批量失效仍只能靠人翻日志发现。
---
## 4. 分期落地
创始人定的范围是"全做",但全做不等于一把上,要按"先撑住可用性达标再优化体验最后跟 k8s 收编"的依赖顺序排
观测栈的落点跟着 [k8s迁移.md](k8s迁移.md) ,统一口径:**目标形态是观测栈作为 mini-desktop 单节点 k3s `observability` namespace workload 部署**(Collector/Prometheus/Grafana/夜莺四件,与被观测的 `huijing-staging` 同集群各自 namespace)。**k3s 就绪前,可先在 mini-desktop 上以裸 docker-compose 把这套栈跑起来过渡**——k8s迁移.md 已明确"观测栈在裸 docker-compose 上也能跑",过渡形态和目标形态对应用侧埋点零差异(都是 OTLP)。这里有一条硬约束要写明:**观测栈不落 mini-infra**。mini-infra 是跨项目共享基建机(new-api/PostgreSQL/MinIO/Gitea 8 个常驻容器),free 仅约 569Mi,k8s迁移.md 定了"mini-infra 的共享基建一律不进集群也不当 k8s 节点"; Prometheus 这种吃内存的时序库塞进去会和 new-api/gitea 抢内存,违背创始人本阶段上 k8s 的裁定所以观测栈和被观测的 staging 同机(mini-desktop), k8s requests/limits 隔离,而不是塞去 mini-infra
**第一期:后端 + 基础设施观测,把可用性和生成失败率看见、告警接通。** 这是优先级最高的一期,因为它直接产出 99.5% 可用性的度量证据和最关键的生成失败率告警内容:在 mini-desktop 上起 OTel Collector + Prometheus + Grafana + 夜莺(k3s observability namespace, k3s 就绪前先 docker-compose 过渡);后端挂 OTel Java Agent引入 actuator/micrometer `ObservationRegistry` NOOP接已有的 SAA 节点 observation新埋生成链路的业务指标(成功率/耗时/积压/九门;成本指标待薄片落地后补);夜莺把 P0P3 规则和飞书/钉钉群通道接通;主机/中间件挂 node-exporter MySQL/Redis exporter。**依赖**:mini-desktop 上的部署资源;成本指标这一项额外依赖 new-api `logs.quota` 采集薄片(尚未落地, §3.2)。完成判据:Grafana 上能看见可用性5xx 生成成功率三张核心图,夜莺能把一条真实告警打到群里
**第二期:admin 观测入口 + 告警通道补全。** admin 控制台加观测菜单,嵌入 Grafana 大盘 SSO 免登跳转; P0 的电话/短信通道随真实化能力补上。**依赖**:第一期的 Grafana 大盘已就绪;SSO 方案6 待核)定了再做;短信/电话通道依赖支付/短信能力真实化
**第三期:前端可观测性。** OTel Web SDK 收前端首屏/JS 错误,打通前后端 traceparent。**依赖**:第一期的 Collector 已就绪;价值是体验优化,不卡可用性,故排最后
**与 k8s 的衔接关系**:上面三期的应用侧埋点不依赖具体部署形态观测栈本身按 k8s迁移.md 的目标落在 mini-desktop k3s observability namespace;k3s 就绪前先 docker-compose 过渡从过渡形态收编到 k3s workload ,后端 agent OTLP 端点从 docker-compose Collector 地址改指向集群内的 Collector Service——**应用侧埋点一行不改**,因为埋点产出的是标准 OTLP,对接点是协议不是部署这是 §2 那条"埋点是应用内的事后端是部署的事"目标的兑现方式
```mermaid
flowchart LR
P1["第一期<br/>后端+基建观测<br/>可用性/生成失败率告警"] --> P2["第二期<br/>admin 入口 + 告警通道补全"]
P1 --> P3["第三期<br/>前端可观测性"]
P1 -.->|docker-compose 过渡→k3s workload 收编<br/>埋点零改| K8S["k8s 迁移<br/>(另一份设计)"]
P2 -.-> K8S
P3 -.-> K8S
```
---
## 5. 验收与风险
### 5.1 怎么算做对了
按层给可验证的判据,每条都要能真跑出来不是文档承诺:
- **采集统一可验**:发起一次"一句话生成游戏", Grafana 里用同一个 trace_id 能查到从网关入口 SAA 各节点 new-api 调用的完整 trace,且这条 trace trace_id 等于响应头回写的 `trace-id`
- **生成失败率可见可告警**:Grafana 有生成成功率实时图(对齐 `game_aigc_task` 终态口径);人为造一批失败把成功率压到 70% 以下,夜莺能在 15 分钟内把 P1 告警打到群里
- **可用性可度量**:Grafana 有游戏流 API 可用性 + error budget 燃尽图,数字对得上 health 探测的实际成功率
- **admin 一键进盘**:运营在 admin 控制台点观测菜单,不二次登录就看见核心大盘(SSO 免登真生效)。
- **告警通道真通**:P0P3 各造一条触发,飞书/钉钉群真收到对应分级的通知
- **旁路不咬主链**:把 Collector 停掉,生成主链路和各 API 行为不变(埋点失败静默丢弃),冒烟门仍全绿
最后一条是底线验收:**观测栈本身挂掉,绝不能影响被观测的服务**。
### 5.2 风险
- **观测栈反噬主链路(最该防的)**。埋点同步化上报阻塞agent 拖慢启动,都可能让"看健康的东西"自己变成故障源对策是 agent 异步批量上报Collector 不可达时本地丢弃埋点开关默认关经 profile 显式开并把" Collector 后主链路不变 + 冒烟门全绿"列为硬验收这条优先级最高,因为它直接威胁 99.5% 可用性
- **同机资源挤兑**。观测栈和被观测的 staging 同在 mini-desktop(huijing 单体 + studio + admin + MySQL + Redis 已共存约 15G),再加 Collector/Prometheus/Grafana/夜莺会进一步吃内存这正是落 k3s observability namespace 的用意:靠 k8s requests/limits + QoS 分级给观测栈设内存上限,别让 Prometheus 的时序库按宿主内存自取吃垮同机的 live 后端(对齐 k8s迁移.md requests/limits 解决多服务共存零保护那条,以及历史回收清单 OOM 优先级保护/容器内存硬限,运维基建-019/020)。k3s 就绪前的 docker-compose 过渡期,等价手段是给每个观测容器设 `mem_limit`。**不放 mini-infra**的理由见 §4(它是共享基建机free 仅约 569Mik8s迁移.md 定了绝不进集群)。
- **SkyWalking OTel 并存期的双采集**。收敛到 OTel 的过程中,如果 SkyWalking toolkit 没真正关掉,会出现两套 trace 各采一遍对不上号对策是切 OTel 时在 profile 里显式禁用 SkyWalking 那套(`huijing.tracer` 相关开关关),依赖留着不删(不动 yudao 框架层)但运行时不启用,确保只有一条采集路在跑
- **告警风暴与噪声**。规则配太敏感会一惊一乍配太松会漏报,P0 通道(电话/短信)尤其不能被噪声打爆对策是给每条规则配持续时间窗(`for: N 分钟`)和静默/聚合策略,先在 staging 跑一段观察阈值合不合适再上严格通道
- **脱敏没在 Collector 兜住**。trace 和日志里可能带手机号/token,如果指望每个埋点点各自脱敏一定会漏对策是把脱敏作为 Collector 的强制 processor 集中做,作为"任何数据进存储前的最后一道",对齐 `security-and-reliability.md` §6 的脱敏红线
- **trace context 跨边界断裂**。前端 `traceparent`yudao `trace-id` OTel W3C 标准三者如果没对齐,trace 会在前后端边界或网关处断成两截,排障时串不起来这是 §6 第一条待核,落地第一期就要先打通这根
---
## 6. 待核与开放问题
下面几条是设计里现在没有十足把握需要落地时核实或创始人拍板的,逐条列清,不臆造:
- **trace context 头怎么对齐**:OTel W3C `traceparent`,yudao `TraceFilter` 回写的是自定义 `trace-id` ,SkyWalking 又是另一套三者怎么统一成一根 trace_id(让前端拿到的响应头回的Grafana 里查的是同一个),需要在第一期落地时按 OTel agent 的实际行为核实并打通。**待核**。
- **trace 后端与日志后端选型**:创始人点了 OTel + Prometheus + Grafana + 夜莺, trace 后端和日志后端这两个都没点具体哪个,同属选型待定trace 后端候选 Tempo / Jaeger,**倾向 Tempo**( Grafana/Prometheus 同体系三联下钻无缝运维一套),需确认资源占用和夜莺侧是否需要 trace 数据;日志后端候选 Loki ,**倾向 Loki**(同属 Grafana ),但和 trace 后端一样属创始人未点名,落地前定。**待核/待拍**。
- **admin 观测入口的嵌入方式与 SSO**:两个待定。① 嵌入方式——iframe Grafana(要开 `grafana.ini` `allow_embedding` + CSP `frame-ancestors``X-Frame-Options`)还是 admin 后端查询接口取数前端自渲(绕开嵌入安全开关但要自己画图), §3.5;② SSO——§3.5 列了 auth-proxy 透传(倾向)和独立账号两条路,前者体验好但要核 Grafana auth proxy iframe 嵌入下的 cookie/同源限制和 admin 代理层的安全边界两者都**待核**。
- **yudao-cloud / vue-pro 的监控集成具体长什么样**:创始人让参考它省搭建工,但它具体集成了哪些(Spring Boot Admin?Grafana 嵌入?自带 Prometheus 配置?)需要实际查证 yudao monitor 模块和前端监控菜单再决定借鉴哪部分,别假设。**待核**。
- **夜莺和 Grafana alerting 的边界**:两者都能告警,本设计按"Grafana 主看板夜莺主告警"分工,但如果夜莺接 Prometheus 数据源有适配成本,是否退而用 Grafana 自带 alerting 顶一部分,需落地时权衡。**待核**。
- **观测数据保留期与成本**:`security-and-reliability.md` §6 定了日志 ERROR/WARN 90 INFO 30 ;trace metrics 的保留期没定trace 数据量大,trace 后端存多久Prometheus 指标存多久,直接影响 mini-desktop 的磁盘(75G)和那笔 ¥500/月监控预算够不够。**待拍**(创始人对一下账:预算里留的 ¥500/月监控冗余,够不够这套栈的存储)。
- **生成成功率告警阈值用 70% 还是别的**:`security-and-reliability.md` §6 写的 P1 "生成成功率<70%",但验收线是 80%。70% "已经很糟该紧急处理"的线,80% "达标线"。是否要在 70%(P1 紧急)之外再加一条 80%(P2/趋势预警),让成功率从 80% 往下掉时就先有预警而不是等到 70%,**待拍**。