bigywater.tech
微服务 · 66

OpenTelemetry SDK 的批量导出与丢弃策略

追踪数据发给后端的路上会在哪里丢?BatchSpanProcessor 的队列满了会怎样、会不会拖慢业务?后端慢一点、挂一会儿,丢多少?进程退出时队列里的 span 怎么办?这篇用 OpenTelemetry Python SDK 和自己写的 OTLP 接收端逐项量出来。

1904 / 6000
接收端每个请求处理 1.5 s、默认队列 2048、1000 个 span/秒时丢掉的 span 数(队列 20000 时丢 0)
3072 / 10000
默认队列遇到 0.1 s 内 10000 个 span 的突发时收到的个数
0 / 1000
子进程用 os._exit(0) 结束时接收端收到的 span 数(正常退出是 1000 / 1000)

接收端变慢

实测 · 本机执行输出
  接收端收到 4096 个 span,没收到(丢了)1904 个;收到的序号范围 0~5999;接收端一共处理了 8 个请求;产生 span 的调用(start 到 end)耗时中位数 89 µs、p99 345 µs、最大 6.40 ms;SDK 的 warning:“Queue full, dropping Span.…”× 1
  收到的序号区间(连续的一段算一个区间):0-1023,1494-2005,3020-3531,3952-5999

持续产生速度超过导出能力时,队列装满后丢弃 span,业务线程几乎不受影响。

队列大小

实测 · 本机执行输出
  max_queue_size | 收到   | 丢了   | 收到的序号范围 | 接收端处理的请求数
  ---------------+------+------+---------+----------
  512            | 2560 | 3440 | 0~5999  | 5        
  2048           | 4096 | 1904 | 0~5999  | 8        
  20000          | 6000 | 0    | 0~5999  | 12       
  接收端每次请求处理 1.5 s、一批最多 512 个 span,所以持续的处理能力有上限;产生速度超过它,队列装不下的 span 就被丢弃,队列越大能缓冲的越多,但内存也随之增加

队列越大缓冲越多,但占内存;默认 2048 只够吸收短暂的突发。

突发

实测 · 本机执行输出
  max_queue_size | 收到    | 丢了   | 收到的序号范围 | 请求数 | 平均每个请求体 | SDK warning 条数
  ---------------+-------+------+---------+-----+---------+---------------
  2048           | 3072  | 6928 | 0~9999  | 6   | 38 KB   | 1             
  20000          | 10000 | 0    | 0~9999  | 20  | 37 KB   | 0             
  产生 span 的线程比导出线程快,队列装满之后新来的(或最老的)被丢;队列够大时突发能被吸收

瞬间产生的 span 比队列大时,多出来的被丢掉。

短故障

实测 · 本机执行输出
  故障方式     | 收到   | 丢了 | 收到的序号范围 | SDK 的 warning(去重 × 次数)                       
  ---------+------+----+---------+----------------------------------------------
  (a)503   | 2400 | 0  | 0~2399  | Transient error Service Unavailable enco… × 3
  (b)停掉再重启 | 2400 | 0  | 0~2399  | Transient error HTTPConnectionPool(host=… × 3

导出器的重试加队列可以吸收几秒的接收端故障。

长故障

实测 · 本机执行输出
  收到 3800 个,丢了 2200 个;收到的序号区间:1216-1727,2712-5999;SDK 的 warning:“Transient error Service Unavailable encountered while exporting spans …”× 9;“Failed to export spans batch due to timeout, max retries or shutdown.…”× 2;“Queue full, dropping Span.…”× 2

故障超过导出超时和队列容量时,整批导出失败被丢掉,队列也会溢出。

进程退出

实测 · 本机执行输出
  结束方式                 | 收到   | 丢了   | 接收端处理的请求数
  ---------------------+------+------+----------
  (a)正常退出              | 1000 | 0    | 2        
  (b)os._exit(0)       | 0    | 1000 | 0        
  (c)产生完 1 s 后 SIGKILL | 512  | 488  | 1        
  队列里还没导出的 span,进程不经过 shutdown 就退出(os._exit、被杀)就永远丢了;正常退出时 SDK 的 atexit 钩子会把队列刷出去
ERRORS {}

不经过 shutdown 的退出会丢掉队列里的所有 span。

分步演示

批量导出与丢弃
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。OpenTelemetry Python SDK 1.45.1 的 BatchSpanProcessor 与 OTLP/HTTP 导出器;接收端是我写的 Python HTTP 服务(解析 protobuf、可以延迟或返回 503);每种配置只测了 1 次,收到的序号区间随运行略有不同;丢弃策略(丢最老的还是最新的)是从收到的序号区间推断的,没有读 SDK 源码
    • 没有验证:gRPC 导出器的行为;Java / Go SDK 的批处理参数与丢弃策略;OTLP 导出器的重试参数(退避时间、最大重试次数);导出失败时的指标(例如 otel.sdk.processor.span.queue 相关的 SDK 自监控指标);Collector 一侧的 batch / memory_limiter 处理器与背压;压缩(gzip)对大小的影响
    京ICP备2026067502号-1京公网安备11010502063487号