OpenTelemetry 采样别再全量开了:我把链路存储成本压到原来的 1/5

发布时间:2026/6/9 17:41:11

OpenTelemetry 采样别再全量开了:我把链路存储成本压到原来的 1/5 OpenTelemetry 采样别再全量开了我把链路存储成本压到原来的 1/5说实话我一开始也觉得链路追踪这种东西当然是全量开才安心。直到账单出来。那次我们把 OpenTelemetry Collector 接进生产后一周Jaeger 的 ES 存储量从每天 180GB 直接冲到 900GB。更麻烦的是查询速度也开始明显变慢排查一次超时请求点开 trace 列表要转十几秒值班同学已经开始抱怨“这玩意儿比没接的时候还费劲”。后来我才彻底想明白很多团队不是不会做可观测性而是把“采得越多越安全”当成默认前提。这个前提一旦不拆链路系统迟早会先把自己拖垮。这篇我就把当时那套采样改造过程讲清楚包括我怎么判断该不该从全量追踪撤退怎么落地 head sampling tail sampling 的组合策略以及最后怎么把存储成本压到原来的 1/5同时保住关键问题的定位能力。为什么全量追踪很容易把自己做废全量追踪最诱人的地方是心理上很踏实。出了问题总觉得“反正 trace 都在之后再查”。但现实是大多数请求根本不值得完整存档。健康检查、静态资源、内部低价值轮询、成功率接近 100% 的短链路接口如果全部保留留下来的不是洞察而是噪声。我们当时的问题有三个。第一存储成本失控。高峰期每秒 1.8 万请求平均一个请求拆出 8 到 15 个 span。把日志和指标分开看还不明显一旦 trace 量级飙起来ES 磁盘和索引合并压力就上来了。第二查询体验变差。Jaeger UI 不是不能查而是候选 trace 太多真正有价值的异常请求被埋在大量 200 OK 的成功调用里。值班时找根因反而要先和噪声打架。第三Collector 自己开始吃紧。批处理队列、导出器重试、后端写入限速层层叠加后链路系统本身成了新的不稳定因素。当时我定了一个很朴素的判断标准如果一套观测系统为了保住“可能有用”的数据反过来拖慢了排障效率那它就已经偏离目标了。别急着砍采样先把链路流量分层我后来没有一刀切地把采样率直接砍到 10%因为那样很容易误伤真正关键的流量。更稳妥的做法是先给 trace 分层。我把请求大致拆成四类。第一类是必须保留的异常流量。只要状态码异常、span 标记 error、或者耗时超过阈值就尽量全留。第二类是高价值业务流量。比如支付、下单、登录、风控命中这些链路哪怕成功也值得保留更高比例。第三类是普通在线请求。大部分查询、列表、配置拉取都在这里适合做比例采样。第四类是低价值噪声。健康检查、Prometheus 抓取、后台轮询、静态资源请求原则上能不进追踪系统就别进。这个分层动作很关键因为它决定了后面的采样规则不再是“一把尺子量所有请求”而是按业务价值分开处理。我最后用的是两段式采样入口先削峰出口再挑重点只用 head sampling 有个典型问题请求一进来就决定采不采后面即便变慢、报错也可能早就被丢了。只用 tail sampling 也有代价你得先缓存一段时间的 trace等整条链路结束后再决定保不保留对 Collector 内存和队列配置要求更高。所以我最后落的是组合方案。第一层用 head sampling先在入口挡掉明显低价值流量避免所有数据都冲到后端。第二层用 tail sampling专门保护异常、慢请求和高价值业务把真正值得看的链路兜住。整体逻辑可以概括成一句话先把洪水变成河流再从河里留下金子。第一步在 SDK 或入口网关先做基础过滤如果你的入口已经很清楚哪些请求天然不需要追踪最好在最前面就拦掉。比如健康检查和静态资源我会直接在 SDK 层过滤receivers:otlp:protocols:grpc:http:processors:filter/drop-noisy-spans:error_mode:ignoretraces:span:-attributes[http.target] /healthz-attributes[http.target] /metrics-attributes[http.route] /internal/ping-attributes[http.method] OPTIONSservice:pipelines:traces:receivers:[otlp]processors:[filter/drop-noisy-spans]这一步不要想着做复杂策略目标只有一个把确定没价值的垃圾流量挡在门外。我们当时只靠这一步就把 trace 总量先打掉了大概 22%。第二步普通流量做 head sampling剩下的在线请求我在入口用了概率采样。比例不是拍脑袋定的而是按照每个服务的 QPS 和后端写入预算反推。配置大概是这样processors:probabilistic_sampler/default:hash_seed:22sampling_percentage:15这里我踩过一个坑。很多人会给所有服务统一一个 10% 或 20% 的采样率看起来简单实际上很粗暴。高 QPS 的网关服务和低 QPS 的管理后台本来就不该用同一把尺子。我的做法是API 网关10% 到 15%核心业务服务20% 到 30%低频后台任务50%支付、登录这类关键链路不靠这一层降交给后面的 tail sampling 兜底这样做完以后Collector 的入口流量先稳住了后端写入峰值也跟着降了一大截。真正保命的是 tail sampling不是概率采样真正让我觉得“这套改造值了”的是 tail sampling 上线之后。因为生产里最怕的不是少看几个正常请求而是把真正出错的请求也一起采没了。tail sampling 的意义就是等整条 trace 结束之后再根据结果决定保留谁。下面这份配置是我后来稳定跑了挺久的一版思路processors:tail_sampling:decision_wait:10snum_traces:50000expected_new_traces_per_sec:3000policies:-name:keep-errorstype:status_codestatus_code:status_codes:[ERROR]-name:keep-slow-requeststype:latencylatency:threshold_ms:1500-name:keep-payment-servicetype:string_attributestring_attribute:key:service.namevalues:[payment-service,order-service,risk-service]-name:keep-important-httptype:numeric_attributenumeric_attribute:key:http.response.status_codemin_value:500max_value:599-name:sample-the-resttype:probabilisticprobabilistic:sampling_percentage:5这套配置的核心很直接。报错请求全留。超过 1.5 秒的慢请求全留。支付、下单、风控这些关键服务高比例甚至全留。其余普通请求只保留 5%。这种策略最舒服的地方是值班时你不用再祈祷“希望这个故障请求刚好被采到”。因为真正要命的流量本来就被规则重点保护了。我怎么验证这套采样不是自欺欺人采样方案最怕的是看起来很省钱实际上把诊断能力一起省没了。所以我没有只盯着“量降了多少”而是同时看三组指标。第一组是成本指标每天 trace 写入量、存储占用、ES 索引增长速度、Collector 导出吞吐。第二组是可用性指标Jaeger 查询耗时、trace 搜索成功率、Collector 队列堆积、导出失败次数。第三组是诊断有效性指标最近一周的 P1/P2 故障里关键 trace 是否都能找到慢请求样本是否足够还原问题高价值服务的关键事务链是否还完整。我把改造前后的结果整理成了一张很粗暴但很好用的对比表指标改造前改造后每日 trace 存储量900GB178GBJaeger 平均查询耗时12.4s3.1sCollector 导出失败率2.8%0.3%异常请求 trace 保留率约 91%99%慢请求样本覆盖率约 54%96%这里最关键的其实不是“存储压到 1/5”而是异常请求和慢请求的覆盖率反而上去了。这说明我们删掉的大部分数据本来就不值得留。这几个坑我建议你提前绕开1.decision_wait设太短tail sampling 会漏关键 span一开始我把decision_wait设成了 3 秒结果跨服务调用还没完全收齐Collector 就已经提前做决定了。后来一些慢请求明明很关键却因为尾部 span 晚到被错误判成普通流量。如果你的服务链路深、消息队列多、异步处理多decision_wait一定别太保守。宁可先给 8 到 15 秒也别急着压太低。2.num_traces配太小会把 Collector 内存和丢样一起搞崩tail sampling 不是白来的它需要在内存里暂存 trace。高峰期并发一上来如果num_traces太小新的 trace 会把旧的挤掉最后你以为自己开了策略实际关键链路根本留不住。我的建议是先按峰值RPS * decision_wait粗算一个量级再看 Collector 实际内存曲线去调。3. 只盯错误不盯慢请求很多问题会漏真实生产里很多事故不是直接 500而是先慢再重试再雪崩。你如果只保留 error trace很容易看不见最早的退化阶段。所以慢请求阈值一定要有而且最好按服务层级分别设置不要全站统一一个数字。4. 采样规则没人维护三个月后一定变脏服务名会变接口会扩业务优先级会调整。采样规则不是一次性工程它本质上是一份运行中的业务策略。我后来把关键服务名单和阈值都放进配置仓走变更评审不让它继续野长。一套更稳的落地顺序如果你现在也在被 trace 成本压着打我建议别一步到位按下面这个顺序来先统计过去 7 天哪些流量最吵明确低价值请求清单。在入口过滤健康检查、静态资源、内部心跳这类噪声。给普通在线请求加基础概率采样先把总量降下来。再补 tail sampling保护 error、慢请求和关键业务服务。用真实故障回放验证样本是否足够而不是只看成本下降。给 Collector 加自监控盯住队列堆积、导出失败和内存占用。这个顺序的好处是每一步都能独立验证不会因为一次大改把链路系统直接掀翻。我现在怎么判断一套采样策略是不是靠谱我现在看采样方案不会先问“采样率多少”而是先问三个问题。第一异常请求能不能稳定保留第二关键业务链路是不是被单独照顾了第三观测系统本身会不会因为保留太多无效样本而变慢这三个问题答不清楚采样率写成 5% 还是 50%本质上都只是运气。链路追踪真正的价值不在于把所有请求都留下来而在于出事的时候你能在最短时间里看到最该看的那部分。写在最后我后来挺少再说“全量追踪更安全”这种话了。对大多数团队来说更现实的答案是关键链路全力保普通流量按价值采噪声流量尽早丢。这样你得到的不是缩水版的可观测性而是一套终于能长期跑下去的可观测性。如果你现在的 Jaeger、Tempo 或 ES 已经开始因为 trace 量太大变慢别急着先扩盘。先看看你是不是把太多根本不会再看的请求认真地存了下来。

相关新闻