OTel - DataDog Observability踩坑

问题

项目后端服务器采用的是OTel SDK (Python + Fargate) + DataDog 完成数据的可观测行逻辑。

bash 复制代码
ERROR:opentelemetry.exporter.otlp.proto.http.metric_exporter:Failed to export metrics batch code: 413, reason:  <Exceeded maximum payload size: limit=512 kB received=1.2 MB
ERROR:opentelemetry.exporter.otlp.proto.http.metric_exporter:Failed to export metrics batch code: 413, reason:  <Exceeded maximum payload size: limit=512 kB received=1.3 MB

在生产环境的Fargate容器日志中观察到以上错误:OTel SDK向DataDog发送metrics 数据错误,DataDog返回了413,原因是payload过大,大于512K的限制。并且可以观察到以下几点:

  • 日志不是在容器启动时立即出现的,而是随着容器的运行开始打印
  • 在容器处理请求时,可以看到received数值从1.2M涨到了1.3M

基于以上,猜测OTel SDK 发送的metrics 数据是累加的,随着容器的运行越累积越多,直到超出了DataDog的限制。

这个结论是对的,发生这个问题的原因是OTel 在发送metrics data的时候默认采用的就是全量发送,而DataDog则默认需要的是增量数据。

解决

在初始化 OTLPMetricExporter 时,显式告诉它。这里不同的指标原语根据业务逻辑有的保留了CUMULATIVE累加量模式,有的则改成了DELTA增量模式:

python 复制代码
delta_temporality = { 
    Counter: AggregationTemporality.DELTA,
    UpDownCounter: AggregationTemporality.CUMULATIVE,
    Histogram: AggregationTemporality.DELTA,
    ObservableCounter: AggregationTemporality.DELTA,
    ObservableUpDownCounter: AggregationTemporality.CUMULATIVE,
    ObservableGauge: AggregationTemporality.CUMULATIVE,
}

exporter = OTLPMetricExporter(
    endpoint=settings.DD_OTLP_METRICS_ENDPOINT,
    headers={"dd-api-key": dd_api_key, "dd-otlp-source": "runtime"},
    timeout=30000,
    compression=Compression.Gzip,
    preferred_temporality=delta_temporality,
)
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=10000)

分析

OpenTelemetry 默认使用的 累积时间性 (Cumulative Temporality)。一旦本地的数据累加超过DataDog的限制,则SDK向DataDog的请求会一直失败,失败也不会清空内存里的数据,因此除非容器重启,一旦开始超过limit,OTel SDK的发送就会一直失败,并且payload也会越来越大。

而Datadog 的接口设计是为了处理大规模并发数据的,使用增量时间性Delta Temporality。比如在采点的10s 时间内有哪些metrics数据就发这个差值。payload大小相对稳定。

但是因为OTEL在最初是为了Prometheus设计的,如果不明确指定,它其实采取的累加数据的策略,随着时间的增长,达到DataDog的payload限制,就会发送失败了。这个问题日志提示挺清楚的,一旦出现了也比较好修改。

讨论

  1. OTel 为什么这么设计?
    OpenTelemetry (OTel) 这样设计其实是为了解决分布式系统监控中最致命的问题:网络丢包和数据可靠性。累积模式如果在某一次发送的时候数据丢包,那么内存中的数据是没丢的,下一次发送带上是一样的。但如果是累加,那么丢数据就是丢点了。属于是牺牲了传输效率(Payload 变大),换取了极高的数据精确度。
    另外,OTel SDK 的默认配置主要是为了适配 Prometheus。
  • Prometheus(PULL模式): 后端会向OTel SDK 拉数据,并且拉到数据之后后端做减法,去算那个增量。防止后端拉不到数据或者丢包,为了适配 Prometheus,SDK 必须维护一个不断增长的计数器。
  • Datadog(PUSH推送模式): 此时OTel SDK会主动向后端推送数据,即发送请求,它们的设计就是"增量"系统,更习惯于接收"刚才发生了 5 个错误"这种指令。
  1. 为什么 Counter 和 Histogram 用 DELTA,而UpDownCounter 和 Gauge 要保留 CUMULATIVE?
    Counter它们记录的是"发生的次数", Histogram记录"事件的分布"。物理意义上它们只增不减的,如果不该就是累加payload。
    而UpDownCounter和Gauge这里的 CUMULATIVE 其实代表的是 "当前状态" (Current State),而不是"历史累加"。这个两个量表物理意义上不是 只增不减的,而是维护当前状态的,比如内存,CUMULATIVE计算的是此刻占有的内存是500MB,如果用DELTA则表示这个打点和上个打点的内存差,太不直观了。
    相应地,从代码逻辑上,前两个metrics的值会不断增长,而后两个的值会一直在一个范围内,所以这样修改了之后payload并不会一直增长,可以解决413的问题。

背景

1. Counter、Gauge都是什么?

在监控系统中,这些不同的 metrics 类型被称为指标原语(Metric Instruments)。它们就像是工具箱里的不同工具,专门用来测量不同性质的数据。

理解它们的关键在于两个维度:它是加法还是赋值? 以及 它是同步(主动记录)还是异步(观察取值)?

  1. 基础型(同步指标):你主动去写数据

    这类指标是在代码运行到某处时,你手动调用 add() 或 record()。

    Counter(计数器)

    特点: 只增不减。

    场景: 累计请求数、累计错误数、发送的总字节数。

    UpDownCounter(增减计数器)

    特点: 可增可减。

    场景: 当前活跃请求数、队列中等待的任务数。

    Histogram(直方图)

    特点: 统计分布(最占空间的类型)。

    场景: 接口响应耗时(P99, P95)。它是产生 413 错误的"大户",因为它会产生多个桶(Buckets)。

  2. 观察型(异步指标):SDK 定期来问你

    这类指标带 Observable 前缀。不需要在业务代码里频繁调用,而是给它一个回调函数,每当 PeriodicExportingMetricReader SDK 会定时执行函数取个值。

    ObservableCounter(可观察计数器)

    场景: 当你想监控一个系统本身就在累加的值(比如 CPU 时间、内核收到的总数据包)。不需要自己算,只要让 OTel 每 10 秒去读取一下系统变量即可。

    ObservableUpDownCounter(可观察增减计数器)

    场景: 监控资源消耗。比如:当前进程占用的内存。

    ObservableGauge(可观察测量仪)

    特点: 记录瞬时数值。

    场景: 温度、内存占用百分比、当前的 CPU 使用率。

2. OTel是什么?

OpenTelemetry (OTel) 是一套标准和开源工具集,专门用来收集、处理和导出软件的"观测数据"。在没有 OTel 之前,如果你想用 Datadog 监控,就必须用 Datadog 的 SDK;想换成 Prometheus,就得重新写代码。OTel 的出现就是为了解除绑定:它提供了一套统一的协议。OTel 的工作流程可以分为三个阶段:采集 -> 处理 -> 导出。

3. OTel 是如何工作的?

采集阶段 (Instrumentation)

代码通过 OTel SDK 产生数据。有两种方式:

  • 自动采集: 像你用的 FastAPIInstrumentor,它会自动拦截 HTTP 请求并生成耗时指标。
  • 手动采集: 你手动调用的 counter.add(1),记录特定业务逻辑。

收集与处理阶段 (The Pipeline)

数据在发送前,会在 SDK 内部经过一个"流水线":

  • Resource(资源): 自动给数据打上标签(比如:这是哪台机器、哪个环境、哪个容器 ID)。
  • Processor(处理器): 可以在这里过滤掉没用的指标,或者对数据进行采样,防止数据量太大。
  • Reader(读取器): PeriodicExportingMetricReader,它决定每隔多久把内存里的数据打包。

导出阶段 (Exporter)

这是最后一步,将处理好的数据翻译成后端能听懂的语言。

  • 如果配置了 OTLPMetricExporter 并指向 Datadog,它就会把数据转换成 OTLP 协议发走。
  • 可以随时更换 Exporter 而不需要修改你的业务代码。
相关推荐
tjc199010052 小时前
SQL中如何处理GROUP BY的不可排序问题_ORDERBY与聚合
jvm·数据库·python
Ulyanov2 小时前
《玩转QT Designer Studio:从设计到实战》 QT Designer Studio的定位革命与技术架构
开发语言·python·qt·系统仿真·雷达电子对抗仿真
JoshRen2 小时前
Python使用PyMySQL操作MySQL完整指南
数据库·python·mysql
HHHHH1010HHHHH2 小时前
CSS定位如何实现多行文字垂直居中_通过绝对定位模拟表格
jvm·数据库·python
佩洛君2 小时前
如何在Ubuntu22.04中安装ROS2-Humble
c++·python·ros2
昆曲之源_娄江河畔2 小时前
婴儿版训练GPT
python·gpt·机器学习·大模型训练
pupudawang2 小时前
Spring Boot 热部署
java·spring boot·后端
qq_413847402 小时前
Redis怎样设计企业级备份策略_结合全量RDB与增量AOF实现多级数据保护
jvm·数据库·python
下地种菜小叶2 小时前
Spring Boot 2.x 升级 3.x / 4.x 怎么做?一次讲清 JDK、Jakarta、依赖兼容与上线策略
java·spring boot·后端