- 注册表: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 -- <路径>)
42 KiB
topic, canonical, date
| topic | canonical | date |
|---|---|---|
| 观测体系 | true | 2026-06-21 |
运维域 · 线上观测体系设计
绘境AI 线上观测体系的 canonical 设计稿,回答线上服务跑起来之后靠什么持续看见它、靠什么在它出问题时被叫醒。它补的是运维主档 README §4 立位、但一直留白的那一块:主档 §1.3/§1.4 回答了"部署后跑一遍冒烟门确认健康"和"健康到 ≥99.5% 可用性才算达标",但"线上持续靠什么观测、5xx 飙升靠什么发现、生成失败率掉了谁告诉你"这一问,现行只有一个秒级冒烟门加一个可用性数字撑着,配不上一个要对外服务的平台。 给谁看:搭观测栈和埋点的工程师、值守线上的人、做生成质量与成本对账的人、关心可用性达标证据的创始人。 边界:本稿只管观测本身——采集怎么埋、看板告警怎么分工、admin 怎么进大盘。观测栈的部署落点与 k8s 迁移强相关:创始人 2026-06-21 已定本阶段上 k8s、观测栈随服务一起往 k8s 收(见 k8s迁移.md)。本稿按那份迁移档的目标形态设计——观测栈落在 mini-desktop 单节点 k3s 的
observabilitynamespace;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 28–40,带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 §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)。这条业务数据回路和"前端页面在真实用户浏览器里加载多慢、报了什么 JS 错"这种前端可观测性是两回事,后者现在完全没有。
缺口归纳成三条:
- 没有任何遥测后端在收数据。Micrometer 指标没人 scrape,trace 没地方落,日志还是各进程自己的文件。蓝图里画过的 Prometheus / Grafana / Jaeger 三个容器,MVP 现实从未起过(历史回收判定 运维基建-001)。
- 采集标准不统一,且和创始人选定的不一致。现状脚手架是 SkyWalking + Micrometer 两套各走各的;创始人 2026-06-21 定的是 OpenTelemetry(OTel)一套统一 trace / metrics / log。这不是补一个后端就完,而是要把采集标准收敛到 OTel。
- 告警是一张纯文档表,没有一根通道真接上。
security-and-reliability.md§6 把 P0–P3 升级链和触发条件写全了(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)。整条链的形态如下。
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/>P0–P3 升级链落地"]
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 居中,而不是各组件直连后端。 让每个被观测进程直接把数据推给各自的后端(指标推 Prometheus、trace 推 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 那张 P0–P3 升级链表变成真的规则和真的通知(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<60s、P95<180s"的硬指标。埋点位:Worker 一局生成的起止。gen_queue_depth—— 队列积压(全局在飞 = queued+running)。取数口径要对齐控制平面的真实实现:背压阈值是AigcControlPlaneProperties.queueDepthLimit,代码里当前是占位值 50(注释明确"占位值,需确认",不是蓝图里画过的 500);超阈值时走业务码AIGC_BACKPRESSURE_REJECTED(1_101_001_002),HTTP 仍 200、body.code 非 0,不产生 HTTP 429/503(见 开闸验收门-W-G1 决策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-apilogs.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 贯穿画出来:
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 那张 P0–P3 表现在是纯文档,夜莺要把它落成三样东西:规则(什么条件触发)、分级(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 把 P0–P3 全打通(这是零成本、当天能接的),P0 的"电话+短信"依赖短信网关/电话服务,随真实化的支付/短信能力一起接(现在 P0 先用群里 @所有人 + 短信兜底,电话留接口)。这条避免了"因为电话通道没接就说告警没做"——先把群通知接通,贵的通道按需补。
webhook 密钥(飞书/钉钉群机器人的 webhook URL 与签名 secret)按内网阶段铁律落进 docs/内网凭据与端点.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、配 CSPframe-ancestors允许 admin 源)。如果不想放开 Grafana 的嵌入限制,另一条路是不嵌 iframe、改由 admin 后端的观测查询接口(见下)直接取指标数据、admin 前端自渲核心曲线——避免跨源嵌入的安全开关,但要自己画图。两条选哪条见 §6。 - 跳转入口:对需要深挖的场景(下钻某个 trace、查某段日志),提供直接跳转到 Grafana 对应面板的链接,在 Grafana 里做三联下钻。
这套入口要新建的三件,逐件列清(都是现在没有、要新建):
- 后端管理员专属查询接口:在 admin 侧后端(走
system_users鉴权的 admin-api 段)加观测查询接口,带 yudao 的权限注解(@PreAuthorize("@ss.hasPermission('observ:dashboard:query')")之类),用途有二——给"API 直渲"方案供数(读 Prometheus/夜莺接口聚合后回前端),以及给 SSO auth-proxy 做这层可信代理的落点。接口受权限守门,不能让未授权 admin 用户读到观测数据,更不能把 Grafana/Prometheus 裸暴露出去。 - admin 前端菜单 + 路由 + 组件:在 admin 加"观测"一级菜单、对应路由,和观测大图/跳转入口两个视图组件。这部分前端 file 级细节(具体路由路径、组件文件、菜单挂载点)留在 admin 活代码里落,本稿只定形态。
- 嵌入安全处理:即上面 iframe 方案要处理的
X-Frame-Options/allow_embedding/CSP,或选 API 直渲彻底绕开嵌入。这件和第 1 件的"接口受权限守门"一起,构成观测入口的安全边界。
这里有个必须解决的体验问题:别让运营进个监控还要再登一次 Grafana。admin 用户走的是 yudao 原生的 system_users 身份(和 C 端 game_player 是两套人,见 后端数据模型 §4)。所以 admin → Grafana 要做单点免登。可选两条路,具体选哪条待核(见 §6):
- 反向代理 + 请求头透传:admin 后端做一层代理,把 admin 已登录用户的身份经可信请求头(Grafana 的 auth proxy 模式)透给 Grafana,Grafana 信任这个头直接放行。好处是 admin 和 Grafana 共用一套登录态,运营无感。
- Grafana 独立账号 + 嵌入只读:Grafana 开匿名只读或共享只读账号,admin 嵌入的大盘走只读视图,深挖才跳独立登录。简单但体验差一截。
倾向第 1 条(auth proxy 透传),它最贴合"一键进盘、无二次登录"的要求;但要核 Grafana auth proxy 在嵌入(iframe)场景下的 cookie/同源限制,以及 admin 后端做这层代理的安全边界(别把 Grafana 整个暴露出去)。下面这张图画 admin 进盘的链路:
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)。② 新增指标: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 走,统一口径:目标形态是观测栈作为 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、新埋生成链路的业务指标(成功率/耗时/积压/九门;成本指标待薄片落地后补);夜莺把 P0–P3 规则和飞书/钉钉群通道接通;主机/中间件挂 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 那条"埋点是应用内的事、后端是部署的事"目标的兑现方式。
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 免登真生效)。
- 告警通道真通:P0–P3 各造一条触发,飞书/钉钉群真收到对应分级的通知。
- 旁路不咬主链:把 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 仅约 569Mi、k8s迁移.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、yudaotrace-id头、OTel W3C 标准三者如果没对齐,trace 会在前后端边界或网关处断成两截,排障时串不起来。这是 §6 第一条待核,落地第一期就要先打通这根。
6. 待核与开放问题
下面几条是设计里现在没有十足把握、需要落地时核实或创始人拍板的,逐条列清,不臆造:
- trace context 头怎么对齐:OTel 用 W3C
traceparent,yudaoTraceFilter回写的是自定义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+ 放 CSPframe-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%,待拍。