docs(design): 全链路可观测性 OTel 三信号全栈 落地设计(阶段四草案·待双评审+创始人评审)
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled

方向 B 全栈 OTel(Collector+Prometheus+Tempo存MinIO+Loki+Grafana 三信号)+ 落点 mini-infra
加内存后落(创始人 2026-07-05 两拍)。摸底坐实五面现状(game-cloud SkyWalking 追踪已死需上
OTel Java agent+actuator / Python studio_sink 已是 OTLP exporter 重定向近零成本[requirements 缺
otel wheel 隐雷] / deploy 观测栈零 / 跨语言 traceparent 打通两条 HTTP 接缝 / 业务指标埋点零命中)。
本设计修订 canonical 观测体系.md 三处(落点 mini-desktop→mini-infra扩容 / 告警件夜莺→Grafana 统一 /
补五面落地+埋点清单),sot-impact 已申报、上级指 canonical。§10.3 已写回 mini-infra 实测(15G/free
427Mi/容器实占~7.5G ragflow 独占 3.35G)+ 扩容建议(32G 下限 / 64G 一台管到底)。实施卡硬件扩容窗口。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-07-05 01:01:22 -07:00
parent 5cb84c1ca6
commit fd2546399b

View File

@ -0,0 +1,137 @@
---
date: 2026-07-05
topic: 观测体系-阶段四落地
status: 草案(fable 主笔 · 待 Codex+Opus 双评审 + 创始人评审)· 方向 B 全栈 OTel、落点 mini-infra 加内存(创始人 2026-07-05 两拍)
sot-impact: 修订 观测体系(canonical docs/architecture/运维/观测体系.md)——三处实质改动:①落点从原稿「绝不落 mini-infra」改为「mini-infra 扩内存后落」;②告警件从夜莺 Nightingale 改 Grafana 统一告警(见 §7);③补全五面落地实施与埋点清单。实施收口后回写该 canonical。
上级: docs/architecture/运维/观测体系.md
---
# 全链路可观测性 · OpenTelemetry 三信号全栈 —— 落地设计(配置控制面阶段四)
## 1. 这份设计要解决的问题
到今天为止,这个平台对自己运行状态的了解是零散的。后端 game-cloud 的追踪能力挂在一个已经死掉的 SkyWalking 集成上——注册追踪切面的那段代码整段被注释掉,只剩一个往响应头写 `trace-id` 的过滤器,而这个 trace-id 在没有 SkyWalking agent 的情况下是空串;指标所需的 actuator 和 micrometer 是可选依赖,根本没进生成主线那个单体的 classpath。Python 生成线倒是已经在用 OpenTelemetry 的 SDK 往外发 trace,但发的地方是 AgentScope 自己的 Studio,不是一个能统一查询的后端,而且真跑时因为 requirements 里没声明 otel 的 wheel,惰性 import 一失败就静默降级成不发。生成的成本、token、九门过门率、队列深度这些真正该盯的业务指标,眼下只落在产物目录的 json 文件和回调的 trace 字段里,没有一处能实时看、能告警、能across 服务串起来看。
一句话:**有一堆散落的信号源,没有一个收口的地方,更没有把「用户点一下到模型返回」这条链路端到端串起来的能力。**
阶段四补的就是这一层。方向已经定了:用 OpenTelemetry 作统一标准,建一套三信号(指标 / 追踪 / 日志)全栈——OTel Collector 做统一入口,Prometheus 存指标,Tempo 存追踪(落在既有 MinIO 上),Loki 存日志,Grafana 做统一看板和告警。所有服务、两条生成路(Python AgentScope 路和 Java 进程内 SAA 路)都把信号汇进同一个 Collector,才叫真正的「全链路一把梭」。
**这份设计不是从白纸起。** 架构层已经有一份 canonical 的《观测体系》设计稿(2026-06-21)把这套架构的骨架画出来了。本设计是它的落地修订版:把抽象架构变成能施工的五面实施 + 埋点清单,并就两处与原稿不一致的地方(落点、告警件)给出理由和新结论。实施收口后,结论回写那份 canonical。
## 2. 两个已拍的前提决策
**方向 = 全栈 OTel 三信号(创始人 2026-07-05)。** 不走「先指标后追踪」的分波保守路,也不复用已在框架里但已死的 SkyWalking,而是一次把 Collector + Prometheus + Tempo + Loki + Grafana 的完整标准栈立起来。取的是完整和标准,认的是「现在省的事后面都会找你麻烦」。
**落点 = mini-infra 加内存后落(创始人 2026-07-05)。** 这里有一个必须讲清的冲突:原《观测体系》稿和《k8s 迁移》稿都立过硬约束——观测栈绝不落 mini-infra,因为内存不够。实测数据是 mini-infra 总内存 15G,早先 8 个容器时空闲只有 569Mi;而那还是 Nacos(常驻约 627M)和 RocketMQ(broker 加 namesrv 约 2G)进驻之前的数,现在更紧。再叠一套 Prometheus + Tempo + Loki + Grafana + Collector(经验值约 2 到 4G),直接落几乎必然 OOM,而且炸的是同机的 new-api、gitea 这些跨项目生产依赖。创始人在知道这笔内存账后,选择的不是压瘦硬塞、也不是拆到另一台,而是给 mini-infra 扩内存再落,理由是运维就近、一台管到底。
这个选择带来一个时序推论,恰好对本阶段有利:**实施卡在硬件扩容窗口上,而设计不卡。** 创始人要的本就是「先出设计」,那么设计现在就出、走完评审待命,等内存扩容窗口一到即可施工。设计阶段唯一要做的实机动作,是在 mini-infra 上真跑一次 `free -m``docker stats` 把当前余量和目标容量坐实,给扩容规格一个依据。
## 3. 现状与差距(摸底坐实,给施工一个准星)
五个组件面的真实状态,决定了每一面各自要补多少。
**game-cloud 的客户端地基一半在、生成主线上根本没有。** monitor starter 里 SkyWalking 追踪的 Bean 整段被注释、只剩一个写空串 trace-id 的响应头过滤器;micrometer、prometheus registry、SkyWalking toolkit 在 starter 里全是 optional 依赖,默认不传递;而线上那个聚合十一个业务模块的单体 `huijing-server`,pom 里既不引 monitor starter、也不引 actuator 和 micrometer——也就是说,那套客户端地基对生成主链路的进程而言,classpath 上是不存在的。唯一已经落地的信号是 SAA 图节点的 observation:aigc 模块真引了 Spring AI Alibaba 的 graph observation,每个图节点会发一条带 trace 和耗时的 micrometer observation,代码已经挂好,但因为拿到的 ObservationRegistry 是 NOOP(缺 micrometer),信号产不出来。这一面的结论是:**要上 OTel Java agent 自动织入 span,并引 actuator + micrometer 让那个 registry 不再是 NOOP、让 `/actuator/prometheus` 端点成立。**
**Python 生成线已经在讲 OTLP,只是指向不对。** tier2 和 cheap-worker 两条 Python 线共用一套 observability 模块,其中 studio_sink 已经是标准的 OpenTelemetry OTLP/HTTP exporter,把每步生成组成符合 gen-ai 语义约定的 span(带 conversation id、input/output tokens、tool name)往外发,只是发去了 AgentScope Studio 的 `/v1/traces`。成本和 token 的计算(按 new-api 的额度口径折算人民币)也已经是现成的纯函数,落在 run-summary 和回调的 trace 字段里。这一面几乎不用改架构,重定向到 Collector 的成本极小:给两个 worker 的 requirements 加上 otel 的两个 wheel(sdk 和 otlp-proto-http exporter)、把导出端点从 Studio 改指 Collector 的 OTLP 入口、把 live 路那条默认构造但休眠的导出真开起来。**这里要顺手修一个和阶段二同类的坑:otel wheel 在 requirements 里没声明,惰性 import 时才炸——又一个「已声明未装」或「未声明惰性依赖」的隐雷,埋点开关真开的那一刻才暴露。**
**deploy/infra 现在只有 Nacos 和 RocketMQ,观测栈是零。** 既有两个独立的 docker-compose,都用 host 网络加精简 JVM 直连本机后端。观测栈的自然落点是新增一个 `deploy/infra/observability/` compose,同样复用 host 网络。
**跨服务追踪的接缝在两条 HTTP 上。** 一次生成的流向是:Java 侧建任务分配一个业务 traceId,经 RocketMQ 发一条只有 taskId 的消息(同进程消费,这一跳是 Java 到 Java、不是语言跨界),再二选一——默认走 HTTP 把 job 发给 Python Agent Service、Python 生成完用 HMAC 回调 game-cloud;或走进程内的 SAA 图直调。真正的语言跨界是那两条 HTTP,而它们现在的 header 里都没有 W3C 的 traceparent。现有的业务 traceId 字符串承担了关联的角色,但它不是标准 trace-id。所以贯穿要做的是:在这两条 HTTP 接缝注入和提取 traceparent,并把响应头过滤器里 trace-id 的来源从死掉的 SkyWalking 换成 OTel 的当前 span,让前端拿到的 trace-id 真能在 Grafana 里查到。
**业务指标的埋点在代码里是零命中。** 生成成功率、生成耗时、队列深度、九门过门率、单次成本这些 MVP 要盯的指标,目前要么只落盘、要么只在回调的 trace 字段里,没有一个是以 metric 形式暴露的。这一面要新埋(详见第 6 节埋点清单)。
## 4. 目标架构
整套栈的形状是「多源汇一口、一口分三库、一板看到底」。
```mermaid
flowchart LR
subgraph 源["信号源(全服务 + 两条生成路)"]
GC["game-cloud 单体<br/>OTel Java agent 自动 span<br/>+ actuator/micrometer 指标<br/>+ SAA 图节点 observation"]
PY["Python 生成线<br/>tier2 / cheap-worker<br/>studio_sink OTLP(重定向)"]
INFRA["Nacos / RocketMQ / Sentinel<br/>micrometer 指标"]
FE["game-studio 前端<br/>(三期:OTel Web SDK)"]
end
COL["OTel Collector<br/>统一入口 · 尾部采样 · 批量<br/>OTLP :4317/gRPC :4318/HTTP"]
subgraph 库["三信号存储(mini-infra 扩容后)"]
PROM["Prometheus<br/>指标 · 保留期可调"]
TEMPO["Tempo<br/>追踪 · 存既有 MinIO"]
LOKI["Loki<br/>日志 · 只收 WARN/ERROR"]
end
GRAF["Grafana<br/>统一看板 + 告警规则<br/>指标→trace→日志 三联下钻"]
ALERT["飞书/钉钉 webhook<br/>告警通道"]
GC -->|OTLP| COL
PY -->|OTLP| COL
INFRA -->|scrape /actuator/prometheus| PROM
FE -.三期.-> COL
COL --> PROM
COL --> TEMPO
COL --> LOKI
PROM --> GRAF
TEMPO --> GRAF
LOKI --> GRAF
GRAF --> ALERT
```
统一入口选 Collector 而非让各服务直连各存储,是为了把采样、批量、重试、协议转换这些横切策略收到一处;任一存储抖动或换型,源侧一行不改。追踪后端选 Tempo 而非 Jaeger,是因为它和 Grafana、Prometheus 同体系,能在一个看板里从指标下钻到 trace 再下钻到日志,而且它用对象存储、可以直接复用 mini-infra 上已有的 MinIO,内存底噪比 Jaeger 配它的存储低得多——即便扩了内存,省着用仍是纪律。
## 5. 五面落地
**面一 · game-cloud 接入。** 主路是 OTel Java agent:给 `huijing-server` 的启动加一个 `-javaagent` 参数,零代码自动织入 Spring MVC、JDBC、Redis、出网 HTTP(包括调 new-api)的 span,一步拿到 Java 侧全链 trace,不动业务代码、不进 pom。为了让已经挂好的 SAA 图节点 observation 从 NOOP 活过来,并让 Nacos/RocketMQ/Sentinel 的 micrometer 指标有处可出,给单体引入 actuator 加 micrometer 的 OTLP 或 prometheus registry,agent 的 micrometer bridge 会把这些 observation 一并收走。这一步的效果是把原本平行的 SkyWalking 和 micrometer 两条路,收敛成一条 OTel。SkyWalking 的 toolkit 依赖留着不删(不动 yudao 框架层),只是在 profile 里不启用;响应头过滤器的 trace-id 来源换成 OTel 当前 span。
**面二 · Python 生成线重定向。** 三件小事:给 tier2 和 cheap-worker 的 requirements 补 otel 的 sdk 和 otlp-proto-http exporter 两个 wheel;把导出端点从 AgentScope Studio 改指 Collector 的 OTLP HTTP 入口;确认 live 路(cheap_studio)那条默认构造的导出分支真开。语义约定已经是 gen-ai 标准键,Collector 和 Tempo 直接认。再进一步,给 Python 侧补一个 OTLP 的 metrics exporter,把成本、token、耗时、门结果从「落盘等后端反推」升级成 metric 直发 Collector,这样成本和过门率这类指标不必全靠后端从 trace 字段里拆。
**面三 · 部署编排。** 新增 `deploy/infra/observability/` 一个 compose,把 Collector、Prometheus、Tempo、Loki、Grafana 五件用 host 网络起在 mini-infra(扩容后),每个容器设死内存上限,对齐 Nacos/RocketMQ 那套精简 JVM 的纪律;Tempo 和 Loki 的后端存储指向既有 MinIO;Prometheus 的 scrape 目标覆盖各服务的 `/actuator/prometheus`,保留期和抓取间隔按扩容后的实际余量定。
**面四 · 贯穿。** 在 game-cloud 调 Python Service 的 HTTP header 注入 traceparent(OTel Java agent 出网自动注),在 Python Service 入口提取、在 HMAC 回调 game-cloud 时再注入回去;RocketMQ 那一跳可选地在消息 user property 带上 traceparent(Java 到 Java、优先级低)。业务 traceId 与 W3C traceId 的关系,采「并存关联」:业务 traceId 作为 span 的一个 attribute 保留(它承担产物归位和回调关联的角色),W3C traceparent 负责跨语言贯穿——不强行把业务 traceId 升级成 W3C 格式,避免动一堆归位逻辑。
**面五 · 埋点。** 见第 6 节清单。原则是:能靠 agent/框架自动出的(HTTP 时延、JVM、SAA 节点)不手埋;只有业务语义的指标(生成成功率、队列深度、九门、成本)才手埋,且埋在终态收口处一次埋全。
## 6. 埋点清单(对着 MVP 指标)
每个 MVP 指标落到「哪个组件、什么信号、怎么埋、现在有没有」。手埋的集中在业务语义指标;时延、可用性、trace 这类靠 agent 自动出。
| MVP 指标 | 来源 | 信号 | 埋点方式 | 现状 |
|---|---|---|---|---|
| 生成成功率 ≥80% | game-cloud aigc | metric | 任务终态回填处埋 `gen_task_total{status}` counter,成功率 = 成功 / 总 | 需新埋 |
| 生成耗时 P50/P95 | worker / game-cloud | metric | `gen_duration_seconds` histogram;Python 已算 wallSec,导出即可 | 值有,需导出 |
| 队列深度 | game-cloud 控制平面 | metric | 背压判定处埋 `gen_queue_depth` gauge,阈值对齐 queue-depth-limit | 需新埋 |
| 九门过门率 / 各门失败 | Python 九门 + 后端 | metric | `gen_gate_fail_total{gate}`,数据源 = verdict.guards / trace 字段 | 原始有,需聚合 |
| 单次生成成本 ¥ | Python worker | metric | `llm_cost` ,cost.compute 已按 new-api 口径折¥,导出 | 值有,需导出 |
| 缓存命中率 | Python worker | metric | `llm_cache_hit_rate`,cached token 已抽,导出 | 值有,需导出 |
| 服务可用性 ≥99.5% | game-cloud + Collector | metric | OTel agent 自动出 HTTP server 指标 + actuator health | 需装 agent/actuator |
| feed 首屏 P75<3s | game-studio 前端 | metric/RUM | 现有业务遥测埋点已在;OTel 前端观测排三期 | 业务 RUM |
| new-api 多通道健康 | 后台巡检 job | metric | 新建 job 探活每通道 + 查 key 额度,`llm_channel_up` / `llm_key_quota_remaining` | 需新建 |
| 全链路 trace | 全链 | trace | OTel Java agent + Python OTLP + 两接缝 traceparent | Python 有、Java 无、接缝未通 |
| 集中日志 | 全进程 | log | logback OTLP appender 或 Collector filelog receiver,trace_id 注入 MDC,只收 WARN/ERROR | 需新建 |
| SAA 图节点 span | game-cloud aigc | trace/metric | 依赖已挂,装 micrometer 即活 | 依赖就位 |
## 7. 关键取舍
**追踪后端 Tempo,不用 Jaeger。** 同 Grafana 体系、三联下钻无缝、存 MinIO 内存友好;Jaeger 的独立 UI 优势在「Grafana 统一看板」前提下价值不大。
**game-cloud 用 OTel Java agent 为主、micrometer bridge 收编存量,不用改代码的 micrometer 手织。** agent 一个参数拿全链、还顺手把 SAA observation 和 SkyWalking 两条路收敛成一条 OTel,是方向已定 OTel 后成本最低的路。
**告警件用 Grafana 统一告警,替下原稿的夜莺 Nightingale。** 这是相对 canonical 原稿的一处实质改动,理由是:既然看板已经统一在 Grafana,再引一个独立的夜莺做告警会多一套要维护的组件和一份要对齐的规则,而 Grafana 自带的告警规则 + 飞书/钉钉 webhook 已足够覆盖 MVP 的阈值告警;少一个常驻件,也少吃一份 mini-infra 的内存。夜莺的优势在大规模多租户告警治理,MVP 用不上。**此项请创始人在评审时确认——它推翻了原 canonical 的一个件选型。**
**采样、保留、内存上限即便扩了内存仍是纪律。** Collector 做尾部采样(错误和慢 trace 全采、正常按 5 到 10% 采),Tempo 和 Loki 存 MinIO,Prometheus 保留期起步压到 7 到 15 天、抓取间隔放宽,每个观测容器设死内存上限。扩内存是为了不 OOM,不是为了放任。
## 8. 旁路铁律与验收
**观测栈挂掉,绝不能咬主链——这是硬红线。** OTel agent 异步批量上报,Collector 不可达时本地丢弃而非阻塞,埋点开关默认关、经 profile 显式开;Python 侧的 best-effort 已是既有铁律(trace 和 studio_sink 全是 try、失败只告警)。硬验收是:**停掉 Collector 后,生成主链行为不变、冒烟门全绿。**
分波实施(每波都在扩容窗口之后,按依赖串):先起观测栈后端五件并自监控;再给 game-cloud 挂 agent + actuator、让指标和 Java trace 出来;再重定向 Python 的 OTLP、补 Python metrics;再打通两条 HTTP 接缝的 traceparent;最后落业务埋点、建 Grafana 看板和告警规则。每一波的验收锚在「对应信号在 Grafana 里查得到」加「停 Collector 主链不变」。前端 OTel Web SDK 排在这些之后的三期。
## 9. 风险与回滚
最大的风险是内存——即便扩容,观测栈仍可能因保留期或采样配置不当而吃超预期,所以每个容器的死内存上限和 Prometheus 保留期是上线即设、不留「以后再调」的口子。第二个风险是 agent 注入对 game-cloud 启动和性能的影响,缓解是先在 staging 挂 agent 验启动 + 压一轮时延对比,确认 agent 开销在可接受范围再上生产 profile。回滚极干净:观测是纯增量的旁路,回滚 = 摘掉 `-javaagent` 参数 + 关掉 Python 的 OTLP 开关 + 停观测栈 compose,主链和业务代码一行不受影响。
## 10. 待评审确认项
1. 告警件用 Grafana 统一告警替下夜莺(§7)——推翻 canonical 原稿的件选型,请创始人确认。
2. 业务 traceId 与 W3C traceId 采「并存关联」而非「升级贯穿」(§5 面四)——影响排障时的 trace 连续性,请评审拍。
3. mini-infra 扩容目标规格——**已实测坐实(2026-07-05 关机前)**:15G 机、空闲仅 427Mi、available 7.3G 全是可回收 buff/cache、容器实占约 7.5G(ragflow 独占 3.35G 为最大且 AI 件爱涨,rmq-broker 1.31G、nacos 706M 次之)、swap 4G 基本未用。观测栈带死内存上限约 3 到 4G 常驻。**建议扩到 32G(下限:常驻约 11.5G + 约 20G 给 buff/cache 与 spike 余量,干净解决 OOM)或 64G(一台管到底,给 ragflow 与生成负载的长期涨幅留头,一劳永逸)**。创始人 2026-07-05 已启动关机扩容窗口,加多少待其定;装好复验整栈健康后本项收口、扩容后规格回写。