Collector 返回了 200,Span 却哪儿都找不到
目标
当后端停止响应或拒绝请求时,实际测量 Collector 的队列与重试设置会如何改变调用方收到的响应和内部遥测指标,并设计出能在不丢失数据的前提下度过短暂故障的配置。
为什么重要
应用收到 200,并不意味着跨度(span)已经到达存储。队列让调用方与故障隔开,代价是把丢失转移到 Collector 内部;重试只能挽救暂时性失败;一旦超过限度,数据只会留下一行日志和一个指标,然后消失。只有学会并排阅读 otelcol_receiver_accepted_spans、otelcol_exporter_sent_spans、send_failed、enqueue_failed,才能诊断管道中的数据丢失。
已准备的环境
python3 /opt/fixtures/otca_backpressure_lab.py init 会在 /root/otca-export/ 中放入 broken.yaml 和 spans.json(每个请求含 2 个 span)。python3 /opt/fixtures/otca_backpressure_lab.py run 설정.yaml [--backend down|ok|reject] [--backend-after 초] [--requests N] [--wait 초](占位符依次为配置文件名与秒数)会真正启动 lab-k8s 中的 otelcol-contrib 0.116.0,每隔 0.2 秒发送一次请求,等待之后读取 Collector 内部遥测中的 otelcol_* 指标并汇总。otlphttp exporter 的 endpoint 会被替换为实验用接收器(down 是无人监听的端口,ok 返回 200,reject 返回 400)。评分器会在相同条件下重新运行你的配置,并与你填写的数字对照。
步骤
- 用
python3 /opt/fixtures/otca_backpressure_lab.py init生成材料后,运行otelcol-contrib validate --config /root/otca-export/broken.yaml。在/root/otca-export/01-validate.txt中写入missing_component=(错误所指向的不存在的组件),并创建改为导出到管道中已定义的otlphttp/backend的/root/otca-export/fixed.yaml。 - 由 fixed.yaml 创建
/root/otca-export/no-queue.yaml,并在otlphttp/backend中加入sending_queue.enabled: false与retry_on_failure.enabled: false。将python3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/no-queue.yaml(后端 down,5 次请求 × 每次 2 个 span)的结果按codes=、accepted_spans=、refused_spans=、send_failed_spans=抄入/root/otca-export/02-no-queue.txt。 - 在
/root/otca-export/queue.yaml中启用sending_queue与retry_on_failure(其他值保持默认)。以同样的方式(后端 down)运行,并在/root/otca-export/03-queue.txt中写入codes=、accepted_spans=、sent_spans=、send_failed_spans=。 - 在
/root/otca-export/small-queue.yaml中放入sending_queue: {enabled: true, queue_size: 2, num_consumers: 1}与retry_on_failure: {enabled: true, initial_interval: 1s, max_interval: 1s}。以后端 down 运行,并在/root/otca-export/04-full.txt中写入codes=、accepted_spans=、refused_spans=、enqueue_failed_spans=、queue_size=。 - 用
--backend ok --backend-after 2.5 --wait 6运行同一个 small-queue.yaml(2.5 秒后接收器启动)。在/root/otca-export/05-recover.txt中写入accepted_spans=、sent_spans=、send_failed_spans=。 - 用
--backend reject(接收器返回 HTTP 400)运行 queue.yaml。在/root/otca-export/06-permanent.txt中写入codes=、sent_spans=、send_failed_spans=、dropping_logged=(日志中是否出现过 Dropping data,true/false)、retried=(接收器收到的请求数 backend_requests 是否多于 5 次请求,true/false)。 - 在
/root/otca-export/short-retry.yaml中把retry_on_failure设为initial_interval: 500ms、max_interval: 500ms、max_elapsed_time: 2s(队列保持默认)。以后端 down、--wait 6运行,并在/root/otca-export/07-give-up.txt中写入accepted_spans=、sent_spans=、send_failed_spans=、dropping_logged=。 - 设计
/root/otca-export/resilient.yaml。评分器会在--backend ok --backend-after 4 --requests 8 --wait 8条件下运行,检查调用方的拒绝为 0 次、16 个 span 在 8 秒内全部发送、失败为 0 次。先用python3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/resilient.yaml --backend ok --backend-after 4 --requests 8 --wait 8确认。
参考
- 接受(accepted)与拒绝(refused)是 receiver 的指标,发送(sent)、发送失败(send_failed)与入队失败(enqueue_failed)是 exporter 的指标。
- 常见错误:把 200 响应当作已送达;认为把队列调大就不会再丢数据(还取决于重试上限、内存和重启);以为 400 也会被重试。
- 本实验的队列是内存队列,Collector 重启后会被清空。持久化队列(storage 扩展)不在讨论范围内。这些数字是在此版本和短时实验条件下测得的值。
- Exporter helper、Internal telemetry
启动前就能发现的错误
用 python3 /opt/fixtures/otca_backpressure_lab.py init 生成材料后,运行 otelcol-contrib validate --config /root/otca-export/broken.yaml。在 /root/otca-export/01-validate.txt 中写入 missing_component=(错误所指向的不存在的组件),并创建改为导出到管道中已定义的 otlphttp/backend 的 /root/otca-export/fixed.yaml。
validate 不会启动 Collector,只检查配置中的引用关系。请对比管道所调用的名称与 exporters 中定义的名称。
既没有队列也没有重试时
由 fixed.yaml 创建 /root/otca-export/no-queue.yaml,并在 otlphttp/backend 中加入 sending_queue.enabled: false 与 retry_on_failure.enabled: false。将 python3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/no-queue.yaml(后端 down,5 次请求 × 每次 2 个 span)的结果按 codes=、accepted_spans=、refused_spans=、send_failed_spans= 抄入 /root/otca-export/02-no-queue.txt。
没有队列时,exporter 调用会在 receiver 的请求内同步发生。请用响应码确认失败会一路传回到哪里。
收到了 200,发送却是 0
在 /root/otca-export/queue.yaml 中启用 sending_queue 与 retry_on_failure(其他值保持默认)。以同样的方式(后端 down)运行,并在 /root/otca-export/03-queue.txt 中写入 codes=、accepted_spans=、sent_spans=、send_failed_spans=。
有队列时,receiver 在请求入队的瞬间就会返回成功。接受与送达是不同的指标。
队列满了就会收到拒绝
在 /root/otca-export/small-queue.yaml 中放入 sending_queue: {enabled: true, queue_size: 2, num_consumers: 1} 与 retry_on_failure: {enabled: true, initial_interval: 1s, max_interval: 1s}。以后端 down 运行,并在 /root/otca-export/04-full.txt 中写入 codes=、accepted_spans=、refused_spans=、enqueue_failed_spans=、queue_size=。
一个消费者占住第一个批次并不断重试期间,队列里只有两个位置。请求每隔 0.2 秒到达一次。
后端恢复后,什么能存活
用 --backend ok --backend-after 2.5 --wait 6 运行同一个 small-queue.yaml(2.5 秒后接收器启动)。在 /root/otca-export/05-recover.txt 中写入 accepted_spans=、sent_spans=、send_failed_spans=。
已经入队的数据可以靠重试存活,但没能入队而被拒绝的数据,Collector 并没有保存。重新发送是调用方的责任。
400 不会被重新发送
用 --backend reject(接收器返回 HTTP 400)运行 queue.yaml。在 /root/otca-export/06-permanent.txt 中写入 codes=、sent_spans=、send_failed_spans=、dropping_logged=(日志中是否出现过 Dropping data,true/false)、retried=(接收器收到的请求数 backend_requests 是否多于 5 次请求,true/false)。
重试是为暂时性失败(连接被拒绝、503 等)设计的机制。对于“格式有误”的响应,无论发送多少次,结果都一样。
重试也有尽头
在 /root/otca-export/short-retry.yaml 中把 retry_on_failure 设为 initial_interval: 500ms、max_interval: 500ms、max_elapsed_time: 2s(队列保持默认)。以后端 down、--wait 6 运行,并在 /root/otca-export/07-give-up.txt 中写入 accepted_spans=、sent_spans=、send_failed_spans=、dropping_logged=。
max_elapsed_time 到期后会停止重试,并丢弃该批次。调用方已经收到过 200,所以丢失只会留在 Collector 日志和内部指标中。
能扛过 4 秒故障的配置
设计 /root/otca-export/resilient.yaml。评分器会在 --backend ok --backend-after 4 --requests 8 --wait 8 条件下运行,检查调用方的拒绝为 0 次、16 个 span 在 8 秒内全部发送、失败为 0 次。先用 python3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/resilient.yaml --backend ok --backend-after 4 --requests 8 --wait 8 确认。
需要两样东西:一是能容纳故障期间到达的所有批次的位置(重试中的批次由消费者占着,所以位置要同时看 queue_size 与 num_consumers),二是后端恢复后,在等待时间内出现第一次重试的间隔。请在文档中确认默认的首次重试间隔。