💡 没有可观测性的系统就是黑盒:线上出问题只能靠猜。今天把Agent变成玻璃盒——每一次LLM调用、每一个tool执行都能追踪。
引导问题:
答案揭示:三大支柱各司其职
| 支柱 | 回答什么问题 | 例子 |
|---|---|---|
| Logs | 发生了什么(离散事件) | "user 123 sent message" |
| Metrics | 系统现在怎么样(聚合数值) | p99延迟2s, QPS 50, error rate 0.5% |
| Traces | 一次请求经历了什么(因果链) | req-abc → LLM(200ms) → search(50ms) → LLM(300ms) |
三者缺一不可:
引导问题:
答案:OTEL = 标准化API + Vendor-neutral
你代码里只写tracer.Start(),要换后端改config就行。
OTEL三大组件:
引导问题:
答案揭示:
推荐span粒度:
原则:每个外部调用 + 每个关键决策点 = 一个span。
main.go集成:
生产注意:
AlwaysSample()会打爆后端,生产用TraceIDRatioBased(0.01)(1%采样)WithBatcher会攒批发送,减少网络开销关键模式:
SetAttributes记录(注意不要记录敏感信息)RecordError + SetStatus(codes.Error, ...)defer span.End() 保证无论如何都关闭每个HTTP请求自动生成root span,包含method、path、status code。
使用:
API暴露/metrics端点(用otelprom桥接)。
核心机制:trace信息通过context.Context在函数间传递。
跨服务传递:HTTP header里的traceparent带trace ID,OTEL的propagator自动读写。
原则:错误请求一定要采样,正常请求抽样。
/chat handler打root span/metrics端点Q: span太多会不会影响性能?
A: 每个span约几微秒开销,主要瓶颈在exporter网络IO。用BatchProcessor攒批 + 低采样率即可。
Q: 怎么debug没有trace的情况?
A:
InitTracer是否成功otel.SetTracerProvider(tp)是否调用stdouttrace exporter打到stdoutQ: 敏感信息能打进span吗?
A: 绝对不要!user_id、conversation_id可以;用户消息内容、API key、PII数据不能。用ScrubDatahook过滤。
Q: Prometheus和OTEL Metrics什么关系?
A: OTEL Metrics是标准,可以导出为Prometheus格式。现代做法:代码用OTEL API,backend用Prometheus。
Q: p99延迟突然高怎么排查?
A:
题目: m×n 网格,从左上角到右下角,每次只能向右或向下走一格,共有多少种不同路径?
思路: 经典二维 DP。dp[i][j] 表示到达格子 (i,j) 的路径数,转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-1],首行首列全为 1(只能直走)。
复杂度: 时间 O(m×n),空间 O(m×n);滚动数组可优化到 O(n)。
变体/面试追问:
题目: 给一个整数数组 nums,找连续子数组的最大乘积,返回该乘积。
思路: 由于负数×负数会变正,不能只维护最大值。需要同时维护当前最大值 maxP 和当前最小值 minP。遇到负数时先交换两者,再分别更新。
复杂度: 时间 O(n),空间 O(1)。
变体/面试追问:
max(n, ...) 自然处理)题目: 爬 n 级台阶,每次可以爬 1 或 2 级,有多少种不同的爬法?
思路: 到第 n 级只能从第 n-1 级跨 1 步或从第 n-2 级跨 2 步到达,因此 f(n) = f(n-1) + f(n-2)——斐波那契变体。用滚动变量做到 O(1) 空间。
复杂度: 时间 O(n),空间 O(1)。
变体/面试追问:
f(n) = f(n-1) + f(n-2) + f(n-3),同理滚动三个变量)明天我们会(难度⚠️):
准备问题: