深入 OpenTelemetry 06 - W3C Trace Context 如何跨越 HTTP 服务
订单服务在 JVM 内有当前 Span,并不意味着库存服务在另一个 JVM 能读到它。一次 HTTP 请求只能传送 header;服务端还必须把 header 还原成父 Context,创建服务端 Span 时显式指定这个父 Context。这里要核对的是 Trace ID 是否继承、parent Span ID 究竟指向哪一个客户端 Span,以及 remote 位何时出现。 模型:线上是字段,线程里是 Context 123order Span -> inventory-client Span -> inject(traceparent) -> HTTP |inventory HTTP handler -> extract(remote parent) -> inventory Span 本篇使用 W3C Trace Context Level 1,2021-11-23 Recommendation:traceparent 的 version...
深入 OpenTelemetry 07 - Baggage 与 Span Link 的两种关系
订单服务需要把租户等级传给库存服务;另一个聚合任务从两个独立输入产生结果。前者是随请求走的业务上下文,后者是一次 Span 与多个输入 Span 的因果关联。不能把这两件事都塞进 parent ID:一个 Span 只有一个父亲,但可以关联多个 Link;Baggage 即便跨服务存在,也不会自动变成导出 Span 的属性。 区分两条传播路径 对象 作用 实验输入 验证出口 Baggage Context 携带键值,显式注入 baggage 头 白名单 tenant_tier=gold inventory 响应 tier=gold Span Link 新 Span 与既有 SpanContext 的非父子关系 两个不同 Trace 的输入 聚合 SpanData 两条 Link Span attribute 新 Span 自身记录的属性 本实验不设置 聚合 SpanData Attributes 为空 订单 HTTP handler 调用注入,库存 handler 在自己的线程提取;Lab07Test 的 Link 实验则完全发生在单 JVM 中,不...
深入 OpenTelemetry 08 - 头部采样:记录不等于导出
同样创建一个 Span,为什么调用方看到 isRecording() 为真,内存 exporter 却没有收到它?把“记录”“sampled 位”“处理器回调”“最终导出”合并成一个布尔值,容易把采样策略误判成网络故障。本篇用三个固定决策把这四道边界拆开。 决策在哪里发生 Sampler.shouldSample 的输入含父 Context、Trace ID、Span 名称、Kind、Attributes 和 Links。调用者是创建 Span 的业务线程,而不是后台 exporter。其返回值 SamplingResult 包含 DROP、RECORD_ONLY、RECORD_AND_SAMPLE 三种决策;此处用按 Span 名选择决策的教学 Sampler,不是 Java SDK 的默认采样配置。 决策 isRecording() sampled 位 本篇 onStart/onEnd 默认 Simple 导出 DROP false false 均无 无 RECORD_ONLY true false 均有 无 RECORD_AND_SAMPLE tru...
深入 OpenTelemetry 09 - SpanProcessor 的调用线程与批量队列
出口变慢时业务线程会不会跟着变慢?把同步导出换成 Batch 处理器后,线程可能不再被 exporter 阻塞,但队列容量又引出第二种失败:Span 已结束,入队却失败。只有同时检查执行线程、生产数量和实际导出数量,才能区分背压与丢弃。 两条路径的边界 路径 onEnd 所在线程 exporter 所在线程 出口被挡住时 SimpleSpanProcessor 结束 Span 的调用线程 同一调用线程 本篇 Future 尚未完成 BatchSpanProcessor 结束 Span 的调用线程 SDK daemon worker 当前请求可继续;队列满时丢弃 从冻结源码看:SimpleSpanProcessor.onEnd 直接调用 export(spans),不是将 Span 自动推入后台线程;它对未 sampled Span 先返回,exporter 抛运行时异常时记录告警而非回滚业务。SpanExporter.export 返回 CompletableResultCode,所以“同步”是调用发生在同一线程,不保证结果已经异步完成;本实验的教学 e...
深入 OpenTelemetry 10 - OTLP 导出的 HTTP 成功不等于完整接收
SpanData 出现在内存 exporter 里,只能证明 SDK 把 Span 交给本地导出器。若改用 OTLP/HTTP,导出器何时返回成功?服务端报告“部分拒收”时,SDK 能否据此识别完整成功?这两个问题需要一个真实监听 HTTP 的进程内测试端点,而不是再读一遍内存列表。 冻结的协议与链路 本篇选 io.opentelemetry:opentelemetry-exporter-otlp:1.31.0、OTLP/HTTP protobuf,请求明确指向 http://127.0.0.1:<随机端口>/v1/traces;VERSIONS.md 保存 artifact 校验值。未运行 gRPC exporter,不能用 HTTP 的行为替代 gRPC。测试接收端是 com.sun.net.httpserver.HttpServer,属教学实现;它不是 Collector,更不是后端存储。 层次 实验观察 不能推出 SpanData 内存 exporter 构造一条 wire-span 已发网络 HTTP 请求 测试端点收到 /v1/trac...
深入 OpenTelemetry 11 - Flush、Shutdown 与进程退出的三个边界
订单请求完成意味着业务工作已经返回,不意味着后台 Batch 队列已排空。进程退出前调用 flush、正常 shutdown 与强制终止,有什么可观测的差异?本篇把“生成三条”“出口报告成功”“本地 HTTP 端点收到三条”分开记录,而不把它们统称为已落库。 完成信号的范围 阶段 能说明 不能说明 span.end() Span 的结束逻辑已执行 Batch 已取出队列 forceFlush().join(limit) 在期限内等待已有处理器完成 超时后结果必然失败或服务器持久化 shutdown().join(limit) 处理器关闭路径得到结果 Collector 或后端可查询 测试端点收到 protobuf HTTP 路径确实有网络接收 数据会长期保留 固定 SHA 的 SdkTracerProvider.forceFlush 组合处理器结果;BatchSpanProcessor.forceFlush 通知 worker 排空当前队列,worker flush 循环 导出队列里的数据,然后完成对应 result。shutdown 先 flu...
深入 OpenTelemetry 12:Instrument 选择与异步观测
同一笔请求需要几种指标? 订单服务处理两笔请求,第一笔结束时仍有另一笔在执行;两笔延迟分别是 10、30 毫秒,而待处理队列当前有 2 项。若把四个事实都写成 Gauge,下一次采集就无法从当前值恢复请求总数或延迟分布。Instrument 的选择先取决于事件的数学语义:单调累计、可增减的存量、多次观测的分布,以及在采集时读取的瞬时状态。 事实 调用 固定事件 首次采集 已接收请求 LongCounter.add 1,1 sum=2 在途数 LongUpDownCounter.add +1,+1,-1 sum=1 延迟 LongHistogram.record 10,30 count=2、sum=40 队列长度 ObservableLongGauge 回调 读取原子变量 value=2 四条流都显式传入 route=/orders;同名但不同属性的记录属于不同数据点。这里的队列 2 不是累计发生过两次入队;第二次采集将它更新为 5,请求总数仍为 2。 源码路径:写入与采集不在同一时间 以下为对 Java SDK c25c0a0ee0da01ab2f...
深入 OpenTelemetry 13:一次 add 怎样进入聚合状态
同一个 Counter,为什么会有两个点? 四个工作线程分别接收 50 笔请求,两个线程标注 region=east,两个标注 region=west。期望导出的不是一条值 200 的流,也不是四条值 50 的流,而是两个属性集合对应的点,各有 100 次写入。record/add 不直接发送网络请求;它在与 instrument 和 reader 对应的存储中更新聚合状态。 聚合键 写入线程 Counter sum Histogram count / sum region=east 2 × 50 次 100 100 / 1000 region=west 2 × 50 次 100 100 / 1000 沿固定源码走一遍 以下均是对 Java SDK 1.31.0 完整 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的静态阅读。SdkLongCounter.add 校验非负后将值、属性、上下文交给 storage。DefaultSynchronousMetricStorage.recordLong 先取对应 han...
深入 OpenTelemetry 14:View 在聚合前改变哪些维度
请求 ID 应该进入指标点吗? 两条路由交替处理 2001 次请求:/orders 1001 次、/stock 1000 次。如果每次事件都附带唯一 request.id,不筛选时理论上出现 2001 个属性集合。路由有聚合意义,单次请求 ID 没有:它让每次写入都新建数据点,且超过 SDK 默认上限。View 可以在聚合前保留 route,去掉 ID;这和“导出时遮盖 ID”不是同一个步骤。 配置 进入存储的属性键 采集结果 默认 route + request.id 2000 点:1999 个独立键、1 个溢出键(值 2) 匹配 View route 2 点:/orders=1001、/stock=1000 固定源码中的选择与上限 下面是对 SDK 完整 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的源码与上游测试阅读。SdkMeterProviderBuilder.registerView 注册 selector 与 View;InstrumentSelectorBuilder.setName 接受名称匹...
深入 OpenTelemetry 15:Reader 的独立时间窗与 Temporality
第二次采集应得到 3 还是 5? 对请求 Counter 先加 2,采集;再加 3,采集。第二次采集的 3 与 5 都可能正确:前者是第二个增量窗口,后者是自 provider 启动以来的累计值。数值本身无法解释边界,必须同时检查 AggregationTemporality 和点的起止时间。一次采集并不意味着所有 reader 的内部聚合都被清零。 reader 第一次(启动→1s) 第二次(1s→2s 或启动→2s) Delta 2,起点为 provider 创建时刻 3,起点为本 reader 上次采集时刻 Cumulative 2,起点为 provider 创建时刻 5,起点仍为 provider 创建时刻 SDK 如何维护这些边界 以下为固定 Java SDK SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的源码阅读。MetricReader 由 provider 注册后获得 CollectionRegistration,读者自己决定何时采集;SDK 提供的 InMemoryMetricReader.c...

