去年底双十一大促,我们的订单服务突然告警,P99延迟飙到2秒。我盯着自研的ELK日志系统,翻了半小时的trace_id关联日志,才定位到是库存服务调用的一个慢SQL。那时候我就意识到,靠日志做全链路排查太被动了——日志是离散的,而请求是连续的。
我们之前的ELK方案(Elasticsearch 7.17 + Filebeat 8.4)其实已经做了不少定制:通过MDC注入trace_id,在Nginx层生成,透传到Java/Go服务。但问题很明显:
span,一旦某个服务漏了日志,链路就断了。去年有个新来的同学写的Python促销服务没加MDC,排查时我们花了3小时才通过Nginx的upstream_response_time反推过去。top,三个工具切来切去。2024年5月Datadog更新了Universal Service Monitoring正式版后,我决定推动团队迁移。核心原因就两个:eBPF的无侵入性和OpenTelemetry的原生支持。
先说eBPF。我们之前用Datadog Agent做APM时,Java服务要挂-javaagent:dd-java-agent.jar,Go服务要引github.com/DataDog/dd-trace-go。但去年我们上线了一个用Rust写的风控服务,官方没有对应的Tracer,只能靠日志。现在Datadog的eBPF探针(基于2024年Q1发布的eBPF Collector v1.2)可以直接在内核层捕获网络包,自动生成服务调用拓扑,不需要改一行代码。我测试时,在AWS EC2上启动eBPF模式后,Rust服务的调用关系直接出现在了Service Map里,连/check接口的耗时都抓到了。
再看OpenTelemetry兼容性。我们之前担心迁移到Datadog会被厂商锁定,毕竟OTel已经是行业标准。但Datadog 2024年的更新里明确支持OTel Protocol(OTLP)原生接入,不需要通过Agent中转。我写了个简单的OTel Collector配置,把我们现有的OTel埋点数据直接发到Datadog:
这个配置跑在我们K8s集群的otel-collector Deployment里,原来的Java服务用的opentelemetry-javaagent.jar(1.32.0版本)不用改任何代码,只需要在启动参数里加-Dotel.exporter.otlp.endpoint=http://otel-collector:4317,数据就直接进Datadog了。对比之前ELK方案里要改logback.xml加trace_id字段,这个迁移成本几乎为零。
还有个细节:Datadog的持续性能分析(Profiler)是始终在线的,开销只有3%左右。我们之前用async-profiler做Java性能分析,得手动触发,而且只能抓几分钟。现在Datadog的Profiler自动关联trace,有次发现订单服务的OrderService.create方法CPU占用高,点进去直接看到是JSON序列化时用的FastJSON在循环里创建SerializeConfig,换成Jackson后CPU降了15%。
我们那个电商订单系统,日常QPS 8000,大促时峰值能到3万。去年10月,运营反馈用户下单后经常转圈超过5秒。我打开Datadog APM的Service Page,找到order-service,看到P99延迟从平时的200ms飙到了1200ms,错误率也从0.1%涨到了2.3%。
先点进分布式追踪的Trace列表,筛选duration > 1000ms的trace。随便点开一个,链路图显示:
gateway -> order-service -> inventory-service -> mysql
其中inventory-service的/deduct接口耗时980ms,占整个请求的82%。再点开这个span的代码级可见性,直接关联到堆栈追踪,看到最后一行是com.inventory.dao.StockDao.selectBySkuId执行的SQL:
我复制这个SQL到Datadog的数据库监控页面,看到它的平均执行时间是920ms,扫描行数10万+。点开执行计划,发现warehouse_id字段没加索引——我们之前做分库分表时,只给sku_id加了索引,忘了warehouse_id也是常用查询条件。
但问题没这么简单。我再看这个trace的服务映射,发现order-service调用inventory-service用的是同步HTTP调用,超时设置的是3秒。而inventory-service当时因为慢查询,连接池被打满(HikariCP最大连接数50,当时活跃48个),导致后续请求排队。
我先在测试环境复现:用sysbench模拟1000个并发调用/deduct接口,确认加索引后SQL执行时间降到12ms。但上线后,P99延迟只降到了400ms,还是比平时高。这时候我注意到Datadog的实时性能仪表盘里,inventory-service的JVM GC次数在慢查询期间飙升——原来慢查询导致大量数据加载到内存,触发了Full GC。
我翻出当时的JVM参数:-Xmx2g -XX:+UseG1GC,G1的MaxGCPauseMillis设置的是200ms。但单次查询返回10万行数据(虽然实际只需要1行),导致Eden区瞬间占满,Mixed GC频繁。我改了两个地方:
warehouse_id索引上线后,我再抓一个trace看:inventory-service的/deduct耗时降到15ms,整个订单请求耗时120ms,P99回到正常水平。这次排查从开始到解决只用了40分钟,要是用之前的ELK方案,光关联日志就得花1小时,还得手动去数据库看执行计划。
我们接入Datadog APM是2024年3月开始的,先拿订单系统做试点,4月全量上线。我整理了接入前后3个月的核心数据,都是生产环境真实跑出来的:
| 指标 | 接入前(ELK方案) | 接入后(Datadog APM) | 变化幅度 |
|---------------------|-------------------|-----------------------|----------|
| 订单接口P99延迟 | 850ms | 120ms | -85.8% |
| 故障平均定位时间 | 47分钟 | 12分钟 | -74.5% |
| 慢查询发现周期 | 平均7天 | 实时告警(<5分钟) | -99% |
| 每月存储/计算成本 | 8200元(ES集群) | 3500元(Datadog按量) | -57.3% |
有个数据特别有意思:我们之前用ELK时,慢查询平均7天才被发现,因为得人工写定时任务扫描日志里的duration > 500ms的记录。现在Datadog的智能告警基于动态阈值,某个接口的延迟比过去7天的P99高20%就自动告警,我们上周有个新上线的优惠券接口,上线10分钟就因为N+1查询被告警,避免了大促时出问题。
再说说冷启动优化。我们去年上线了Serverless的秒杀服务,用AWS Lambda(Node.js 18 runtime),接入Datadog APM后,冷启动时间从1.2秒涨到了1.8秒——APM的Node.js Tracer(dd-trace v4.24.0)在初始化时要加载插件、建立连接,增加了600ms开销。
我查了Datadog 2024年的文档,发现他们针对Lambda做了冷启动优化模式:在serverless.yml里加个配置,让Tracer在Lambda的/tmp目录缓存初始化状态,下次冷启动时复用。我改了Lambda的配置:
然后在代码里,我只对核心的seckill函数做手动instrumentation,非关键逻辑不创建span:
优化后,Lambda冷启动时间降到了1.3秒,只比接入前多了100ms,而慢请求追踪的覆盖率还是100%。我们测了100次冷启动,平均耗时1.28秒,满足秒杀场景的要求(用户感知阈值一般是2秒)。
还有个细节:Datadog的RUM关联帮我们解决了前端抱怨的"后端慢但不知道慢在哪"的问题。现在前端报错时,RUM会自动关联后端的trace,上个月有个用户反馈下单失败,前端同学直接点RUM里的trace链接,看到是payment-service的第三方支付接口超时,我们立刻联系支付渠道排查,比之前前端提工单、后端查日志的流程快了2小时。
去年双十一大促,我们那个订单核心链路突然告警,QPS 飙到 1.2 万的时候,Datadog 后台看到的 Trace 覆盖率直接从 95% 掉到了 40% 不到。我当时盯着 Service Map 里断掉的调用线,第一反应是 Agent 是不是挂了,结果上去一看,CPU 才用了 30%,内存也正常。后来翻了 Datadog 2024 年 5 月更新的 Agent 文档才搞明白,高并发下默认的 apm_config.max_events_per_second 只有 500,超过这个阈值的 Span 直接就被丢弃了,根本不会上报。
我之前一直以为 Agent 会无脑把所有数据都发上去,其实不是。Datadog Agent 为了不把业务机器的资源吃光,内置了一个限流机制。如果你不手动调,默认每秒只处理 500 个事件(包括 Span 和 Metric)。我们那次大促,一个下单请求平均产生 8 个 Span,1.2 万 QPS 就是 9.6 万个 Span/秒,远超 500 的限制,不丢才怪。
解决起来其实不难,改 Agent 配置就行。我们在 datadog.yaml 里加了这段:
改完重启 Agent,覆盖率立马回到 98%。不过这里有个坑,别一上来就设成 10000。max_events_per_second 设太高,Agent 的 goroutine 会疯狂抢占 CPU,我们当时有一台机器设成了 8000,结果业务服务的 P99 延迟反而涨了 15ms,因为 Agent 在做 Span 序列化和压缩时把 CPU 时间片抢走了。后来我们压测发现,8C 的机器这个值设在 5000-6000 之间最平衡,既能保证数据不丢,又不会让业务服务受损。
另一个让我头疼的是 Lambda 环境。我们那个图片处理服务跑在 AWS Lambda 上,平时冷启动就要 1.2 秒,接了 Datadog APM 之后,冷启动直接飙到 2.8 秒,超时率涨了 30%。我查了社区讨论,发现很多人都在吐槽 Lambda 下的 APM 开销。后来我试了 Datadog 2024 年新出的 Lambda Extension 优化方案,把原来的 Layer 方式换成了 Extension,并且关掉了不必要的 Instrumentation。
具体做法是在 serverless.yml 里调整配置:
改完之后,冷启动时间降到了 1.5 秒左右,虽然还是比没接 APM 慢一点,但已经能接受了。这里有个细节,DD_APM_FLUSH_INTERVAL 设成 5 秒,意味着 Span 会在内存里攒 5 秒再批量上报,这样能减少 Lambda 的调用次数,但代价是你在 Datadog 后台看到的数据会有 5 秒延迟。对于非实时告警的场景,这个延迟完全没问题,总比因为 APM 开销导致业务超时强。
还有一次,我们发现某个服务的 Span 总是断断续续,排查了半天,最后发现是服务里用了自定义的 HTTP Client,没有手动注入 Trace Context。Datadog 的自动埋点覆盖不了所有场景,特别是你自己封装的网络库。后来我手动加了这段代码,把上下文传下去:
如果不加这段,下游支付服务的 Trace 就会和上游断开,你在 Service Map 里看到的就是两个孤立的服务,根本没法排查跨服务调用的问题。
上个月我们上线了一个基于 LLM 的智能客服功能,结果用户反馈说有时候回答很慢,有时候又很快。我一开始只看了后端的 APM,发现 LLM 调用的延迟波动很大,从 300ms 到 8 秒都有。但问题是,我不知道用户是在什么场景下触发的,是点了按钮还是发了语音,前端有没有卡顿。后来我把 RUM 和 APM 打通了,才看清了全貌。
Datadog 的 RUM 和 APM 打通其实挺简单的,核心就是那个 trace_id 的传递。我们在前端 SDK 初始化的时候,开启了 trackInteractions 和 traceSampleRate:
前端发请求的时候,SDK 会自动在 HTTP 头里加上 x-datadog-trace-id 和 x-datadog-parent-id。后端只要用了 Datadog 的自动埋点,就会接上这个 Trace。我在后端查 Trace 的时候,点一下就能跳转到对应的 RUM Session,能看到用户当时点了哪个按钮,页面加载了多久,甚至能看到用户当时的屏幕录像(如果开了 Session Replay)。
有一次排查一个慢请求,我通过 RUM 发现用户是在弱网环境下(网络请求耗时 2 秒)触发的,然后后端 LLM 调用又花了 5 秒,整个链路加起来 7 秒,用户肯定觉得卡。如果不打通 RUM,我只看到后端慢,可能会盲目去优化 LLM 的 Prompt,其实问题出在前端没做加载态优化。
说到 LLM 监控,Datadog 在 2024 年 5 月正式推出了 LLM 监控特性,这个对我们太有用了。以前我们监控 LLM 调用,只能看耗时和错误率,现在能看到 Token 消耗、Prompt 内容、甚至模型返回的质量评分。我们在 Python 后端用了 ddtrace 的 LLM 集成:
通过这段代码,我在 Datadog 后台不仅能看到每次 LLM 调用的耗时,还能看到这次调用花了多少钱,用了多少 Token。我们后来发现,有些用户发的 Prompt 特别长,动不动就 2000 个 Token,导致成本飙升。于是我们在前端加了个限制,超过 1000 字的输入就截断,一个月省了差不多 2000 多美元的 API 费用。
更有意思的是,Datadog 现在集成了 AI 驱动的根因分析。有一次我们的订单服务突然报错率升高,我还没来得及去查日志,Datadog 的 AI 助手直接弹了个建议,说是因为数据库连接池满了,并且给出了具体的堆栈信息。我点进去一看,确实是我们那个新来的同事改代码的时候,把连接池的最大连接数从 50 改成了 10,导致高并发下连接不够用。这种 AI 辅助排查,比我们以前一个个翻 Span 快多了。
不过我也发现,LLM 监控现在还不是万能的。比如我们用的那个开源模型,Datadog 的自动埋点覆盖不了,我只能手动去打 Span。还有,Token 消耗的统计有时候会不准,特别是用了流式返回(Streaming)的时候,因为响应是分块回来的,总 Token 数要等流结束才能算出来。我现在的做法是,在流结束的时候,手动去更新那个 Span 的 Tag:
如果不这么做,你在 Datadog 后台看到的 Token 消耗就是 0,没法做成本分析。这也是为什么 Datadog 一直在推 OpenTelemetry 原生支持,因为 OTel 的标准更灵活,能覆盖更多这种非标准的场景。我们最近也在尝试把部分服务从 Datadog Agent 迁移到 OTel Collector,主要是为了以后换监控平台的时候不用改业务代码,毕竟谁也不想被一个厂商绑死。
去年双11前,我负责的一个电商订单服务突然开始报警,P99延迟从200ms飙到1.2s。那会儿我们刚上线Datadog APM没多久,我第一反应就是打开Service Map,一眼就看到订单服务下游的优惠券核销接口全是红色。
我点进去看Trace,发现一个很怪的现象:同一个接口,有的Span只要30ms,有的却要800ms。我本来以为是数据库慢查询,结果翻了半天,发现是冷启动问题。当时我们用的是Lambda处理优惠券逻辑,流量一上来,初始化时间直接把延迟拉爆。
后来我做了两件事:
改完上线,P99直接掉回220ms,那晚我终于没再被电话叫醒。
用了几年Datadog,我自己的体感是:
别一上来就追求“全链路全覆盖”,先想清楚你最怕出问题的那几个接口是谁。把那几条线盯住了,比什么都强。