加入收藏 | 设为首页 | 会员中心 | 我要投稿 站长网 (https://www.haoxinwen.cn/)- 应用程序、AI行业应用、CDN、低代码、区块链!
当前位置: 首页 > 创业 > 资本 > 正文

三年架构设计:从0到10亿的分布式追踪演进之道

发布时间:2026-10-30 14:05:38 所属栏目:资本 来源:DaWei
导读:  凌晨三点零七分,告警平台弹窗叠了十七层,我盯着屏幕右下角那个“trace_id: 0x9f…a3”的红字发呆,手已经悬在键盘上三秒没敲下去——不是卡顿,是本能知道敲了也没用。这个 trace_id 我两小时前刚在 ES 里手动查过,没数

  凌晨三点零七分,告警平台弹窗叠了十七层,我盯着屏幕右下角那个“trace_id: 0x9f…a3”的红字发呆,手已经悬在键盘上三秒没敲下去——不是卡顿,是本能知道敲了也没用。这个 trace_id 我两小时前刚在 ES 里手动查过,没数据;十分钟后它又来了,还是空;再十分钟后,它带着 span_count=0 出现在了采样队列监控图里,像一具被反复拖进太平间的尸体。DBA 老张当时正蹲机柜后换热插拔的 SSD,听见我叹气,头也不抬说:“别查了,上周五那批 tag 压缩逻辑上线后,trace_header 里带 underscore 的 key 全被丢在 ingress filter 之前。” 我原以为是 Kafka 消费端序列化失败,结果翻到第三层日志才发现,是我们自己写的 protobuf 解包函数里,对 unknown_field_set 做了硬裁剪。裁得特别干净,连 warning 都没留一行。


  优胜劣汰?呵。不是市场选出来的,是告警阈值自己长腿跑出去咬死一批服务的。去年十月那次大范围 span 丢失,我们复盘完才发现,压根没人改过采集 SDK,是某次运维批量推送 JVM 参数时,把 -XX:+UseG1GC 换成了 -XX:+UseZGC,结果 G1 的 GC pause 里刚好有 tracer hook 的 safepoint 挂钩点,而 ZGC 没有——整整四小时,所有埋点线程卡在 threadLocal.get() 的那个 volatile 读上,等 GC 安全点。这事最后没进 case,因为值班表上写的是“张工主责”,可张工那天调休申请被驳回,人还在医院陪产。他手机响了一声没接,我看了一眼,微信头像还停在产科走廊灯光下的模糊绿植照片上。


  我敢打包票:你要是把所有“traceId must be non-empty”校验关掉,再把 span start_time 允许为 null,第二天吞吐能涨十八秒——不是优化,是释放了二十万个 goroutine 在等 ctx.Done() 超时。但没人敢关。老板上周拍桌子的时候咖啡洒在工牌上,褐色的渍沿着“Senior Staff Engineer”那行字往下淌,他说:“要的是稳定性,不是吞吐量数字。” 我没接话,只盯着他左手无名指上的婚戒刮花了机柜侧面的漆。后来我偷偷跑了 AB,开 vs 关,结果 QPS 涨了,P99 却毛刺多出三百七十二条告警,全集中在 /api/v2/trace/search 接口。所以我说不准——到底是我们压测脚本写的太温柔,还是 prod 真的就吃不住那个 timestamp_range 查询条件的 index merge 逻辑。


文章配图,仅供参考

  对面工位那个后端,上周换了一双拖鞋,左脚蓝右脚灰,鞋带系法完全不对称。他写了个 span annotation 自动打标规则引擎,逻辑是好的,就是 runtime 里每次调反射都要 new MethodHandle,三个月后堆里飘着四万多个 LambdaForm$MH/0x0000000800cXXXXX。我们杀进程前,监控图里那个内存曲线像在跳踢踏舞。冷风从机房顶吹下来,打在我后颈上,嗡的一声——是空调外机启动的震动传导到金属吊顶,震得我眼镜框有点松。运维小刘路过时顺手帮我拧紧了螺丝,但没提他上个月调休申请被驳回的事。他不说,我知道,因为审批流里最后一个节点钉着我自己的名字。


  三年前搭第一版,用的是 Jaeger 的 all-in-one,单机扛十万 span/s,全链路落盘,disk I/O 直接干到 98%,但大家夸“简单”。两年前上 ClickHouse,schema 设计时争论三天:span 里的 tags 是 flat 还是 nested jsonb?最后选了 nested,因为 DBA 说“将来加字段方便”。结果现在查一个 error rate,光是 JSON_EXTRACT_SCALAR 就占了 query time 的六成。昨天我 ssh 进去看了眼执行计划:先 full table scan,再 where 条件过滤,再 group by,最后 limit 10。中间没索引,也没 partition prune,只有一行注释写着——“这个我说不准,当初建表时文档里没写清楚 CH 对 jsonb 的优化边界”。


  最离谱的是 trace deduplication。我们自研的那个 based-on-root-span-id-and-duration-hash 的去重模块,上线半年后发现,两个完全不同的 trace(一个是支付回调超时,一个是定时任务触发失败),只要 root span duration 都是 1274ms,且 service.name 字段 hash 后低八位一致,就会被合并。不是 bug,是设计如此——当时评审会上没人问“1274 这个 magic number 怎么来的”,只说了句“够用就行”。现在它每天静默吞掉约两千条真正有问题的 trace,而监控面板上 P99 latency 看起来更“健康”了。冷笑话是:这反而让老板更满意,因为他觉得“波动少了”。


  我其实早该想到的。两个月之前,我们给三个新业务线推新采集协议,SDK 版本号打到 v3.5.2,文档里写着“向后兼容”,结果生产环境一半 span 丢了。查到⭐️⭐️⭐️⭐️是因为某个团队的 gradle plugin 会自动 strip 所有 package 名含 test 的类——而我们的 tracing context holder 类路径里恰巧有个 TestSpanStorageHelper。没人改代码,只是 build 工具链悄悄动了一下刀。这种事,你写进架构文档?写进去也没人看。你看我刚才那段是不是又啰嗦了?光说 SSH 进去看了三遍 plan,其实就两步:ssh -i key.pem ch-node-07; clickhouse-client --query="EXPLAIN AST ..."; 然后直接 Ctrl+C。中间那半分钟,我盯着屏幕右下角的系统时间跳变,从 04:22:18 跳到 04:22:17,又跳回来——NTP 同步失锁了,但这跟 span 丢不丢没关系,我只是忽然想起来,上个月值班表里我的名字后面,手写补了个“+1h”,因为那天凌晨三点要重启 core-service,而我不想把交接留给别人。


  所以现在我打算把 span storage 的 compaction 逻辑,从 cron 改成 event-driven,用 CDC 抓 Kafka 的 offset commit 日志做 trigger。不一定比原来快,但至少下次有人想改采样率,不用再挨个 service grep "SampleRate = 0.001"。至于要不要把 tags 重新 flatten 成一百个 varchar 字段?这个我还没想好。明天先跟老张喝杯咖啡,他今早换回皮鞋了,鞋尖擦得锃亮。咖啡渍已经干在工牌上了,摸起来有点涩。

(编辑:站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!