OpenTelemetry、Trace、Span 与采样:调用链怎样记录且控制成本
Span 是有因果和生命周期的操作,不是每个方法一条记录。
OpenTelemetry Java 的 Instrumentation 说明自动、库与手工埋点及信号关联;SDK 配置 给出采样、Span 限额和批量导出队列。
故障现场:给每个私有方法建 Span 造成海量短节点,却在消息生产/消费、异步回调和数据库事务边界断链;导出队列满时应用仍绿色,链路后端却只剩随机片段。
给每个私有方法建 Span 造成海量短节点,却在消息生产/消费、异步回调和数据库事务边界断链;导出队列满时应用仍绿色,链路后端却只剩随机片段。 先画出事件从业务动作到查询结果的完整路径,逐段记录输入、输出、队列水位、丢弃策略和关联键。最危险的不是显式报错,而是应用成功、遥测静默缺失;缺失必须变成可告警状态,不能用“平台偶尔延迟”长期解释。
权威状态与观测结论必须分开
服务入口创建 SERVER/CONSUMER Span,远程出口创建 CLIENT/PRODUCER Span;同一同步调用用父子,批处理、重试和异步因果按语义使用 Link。状态码只表达操作错误。 对每个结论列出反证:字段缺失时还能否区分业务拒绝与系统异常,关联断开时能否从消息或 operationId 恢复,采样后统计是否仍代表总体。信号只能证明其观测窗口内发生了记录,不能证明未记录的事件不存在。
建模主链与边界条件
Extract parent
在 Extract parent 阶段固定 owner、输入、输出、失败原因和容量上限。正向用例证明正常状态产生唯一且稳定的证据;反向用例分别删除上下文、制造未知字段、填满队列或扩大标签空间,断言缺口被计数且不会污染业务线程。字段名和结果枚举一旦进入告警与查询就是契约,修改必须兼容迁移。
Start span
在 Start span 阶段固定 owner、输入、输出、失败原因和容量上限。正向用例证明正常状态产生唯一且稳定的证据;反向用例分别删除上下文、制造未知字段、填满队列或扩大标签空间,断言缺口被计数且不会污染业务线程。字段名和结果枚举一旦进入告警与查询就是契约,修改必须兼容迁移。
Attributes / event
在 Attributes / event 阶段固定 owner、输入、输出、失败原因和容量上限。正向用例证明正常状态产生唯一且稳定的证据;反向用例分别删除上下文、制造未知字段、填满队列或扩大标签空间,断言缺口被计数且不会污染业务线程。字段名和结果枚举一旦进入告警与查询就是契约,修改必须兼容迁移。
Child / link
在 Child / link 阶段固定 owner、输入、输出、失败原因和容量上限。正向用例证明正常状态产生唯一且稳定的证据;反向用例分别删除上下文、制造未知字段、填满队列或扩大标签空间,断言缺口被计数且不会污染业务线程。字段名和结果枚举一旦进入告警与查询就是契约,修改必须兼容迁移。
Sampler
在 Sampler 阶段固定 owner、输入、输出、失败原因和容量上限。正向用例证明正常状态产生唯一且稳定的证据;反向用例分别删除上下文、制造未知字段、填满队列或扩大标签空间,断言缺口被计数且不会污染业务线程。字段名和结果枚举一旦进入告警与查询就是契约,修改必须兼容迁移。
Batch export
在 Batch export 阶段固定 owner、输入、输出、失败原因和容量上限。正向用例证明正常状态产生唯一且稳定的证据;反向用例分别删除上下文、制造未知字段、填满队列或扩大标签空间,断言缺口被计数且不会污染业务线程。字段名和结果枚举一旦进入告警与查询就是契约,修改必须兼容迁移。
从用户问题反推信号,而不是从采集能力出发
可观测不是“系统里有日志、指标和链路”,而是维护者能用外部信号区分内部状态。先写用户可感知的不变量、可能破坏它的故障、权威结果和需要多快做出决定,再选择信号。支付延迟上升需要 Timer 和错误率发现影响面,用 Trace 定位哪段预算耗尽,用结构化日志查看稳定错误码和重试决策;三份信号若只是重复同一句文本,成本增加但证据没有增加。
日志保留离散事件细节,指标把大量事件压成有界时间序列,Trace 保存一次请求的因果路径。指标适合发现“多少、多久、是否异常”,Trace 适合回答“时间花在哪条边”,日志适合解释“这次决策用了什么输入、得到什么结果”。业务数据库、消息位点和外部提供者回执才是权威状态,可观测信号必须指向它们,不能取代它们。
一条信号必须拥有完整生命周期
因此每种信号都有自监控:日志队列深度、丢弃和刷盘;Meter 注册数量、标签基数和抓取失败;Span 生成、采样、导出成功、队列满和 Collector 拒绝。自监控也需要独立故障域,若所有诊断数据只发往同一不可用后端,故障时恰好失明。保留本地有限缓冲、平台健康探针或旁路计数,明确缓冲耗尽后的降级策略。
基数、体积和保留期是运行时资源
日志成本近似事件率乘平均字节和保留期;Trace 成本近似请求率乘每 Trace Span 数、采样率和属性体积;指标更危险的变量是标签组合数,因为每个组合都可能维护独立聚合状态。userId、订单号、完整 URL、异常消息和 traceId 适合日志或 Trace 检索,不适合作指标标签。指标使用路由模板、结果族、依赖名和受控错误码,并在代码评审时计算笛卡尔积上界。
直方图桶、客户端分位数和采样策略都有统计语义。跨实例聚合平均值会掩盖长尾;先平均百分比会让小实例与大实例权重相同;头采样按开始时信息决定,无法预知稍后错误;尾采样需要缓存整条 Trace 并承受乱序和延迟。文章或仪表盘必须写清聚合窗口、分母、缺失数据、实例重启和采样偏差。
敏感数据和攻击面进入信号设计
日志、baggage、Span 属性和错误事件可能携带 Token、Cookie、身份证、SQL 参数和文件路径。脱敏在进入异步队列和 Exporter 前完成,避免明文先落本地缓冲;字段采用 allowlist,异常对象需要检查消息和 suppressed/cause。用于关联的用户维度优先不可逆、可轮换的受控标识,并按诊断需要设置更短保留期。
用故障注入验证信号而不是看一次漂亮页面
至少制造正常、业务拒绝、依赖超时、结果未知、队列饱和、导出失败和上下文切换。每次记录预期指标增量、Span 状态、日志事件、关联完整率和权威业务状态;再证明恢复后水位回落、错误率窗口归零、Exporter 补发或明确丢弃。没有故障前后的差分,页面上出现一条曲线不等于观测正确。
查询、告警与恢复必须共享同一语义
采集端正确不代表消费端正确。为每个关键结论保存一条可执行查询和一组固定输入:总请求、错误、慢请求、结果未知与遥测缺失分别得到确定数量;告警表达持续窗口、最小流量、缺失数据和恢复条件,避免低流量时一个错误放大为百分百,也避免无数据被误判为健康。告警触发后必须能跳转到保留筛选条件的指标、Trace 和日志,而不是让处置者重新猜服务名、时间窗与错误码。
恢复判断使用与触发对称的信号,并增加权威业务水位。依赖延迟恢复不等于积压清空,错误率下降也可能只是入口流量被切走;应同时观察到达率、成功率、队列/线程/连接水位、最老任务年龄和业务对账差异。故障演练记录发现时间、定位时间、缓解时间、信号缺口和无效查询,把改进落到事件 Schema、标签、Span 边界或告警表达式,而不是只修改仪表盘颜色。
审查者还要随机抽取一条成功和一条失败事件,从业务入口反查到最终处置记录,确认字段值来自真实运行路径而非测试常量;再故意关闭一个 Exporter 或填满一个队列,证明缺失计数先于用户投诉出现。版本升级保存事件名、字段 Schema、Meter 名称、标签集合、Span name/kind、语义约定版本和发现数量。先让新旧信号并行,迁移告警与查询,经过完整保留窗口再删除旧信号。任何删除都回放历史事故问题,确认发现、定位和恢复证明仍成立。
两个 Java 17 模型固定可重复见证
模型不依赖日志平台、指标后端或 Collector,只压缩该主题最关键的状态转换和量化判据。真实工程用 SLF4J、Logback、Micrometer 或 OpenTelemetry 驱动同一不变量。
javac --release 17 -Xlint:all -Werror examples/backend-development/observability/otel-trace-span-sampling/TraceTopologyDemo.java examples/backend-development/observability/otel-trace-span-sampling/SamplingBudgetDemo.java
java -cp examples/backend-development/observability/otel-trace-span-sampling TraceTopologyDemo
java -cp examples/backend-development/observability/otel-trace-span-sampling SamplingBudgetDemotrace=t-17 spans=5 roots=1 parentEdges=3 links=1 orphanSpans=0 topologyValid=true
traces=1000 ratio=0.10 headKept=100 errorTailKept=7 exportCapacity=120 budgetMet=true输出变化必须能归因于信号契约、容量策略或业务规格,不能通过删除字段、扩大采样或无限重试吸收差异。
容量、发布与恢复门禁
头采样在根节点决定并沿上下文传播,成本可预测但看不到后续错误;尾采样能保留错误和慢链路,却需要 Collector 缓冲完整 Trace。属性、Event、Link 和值长度都有上限。 发布前用峰值输入和故障放大系数计算容量,在预发布环境制造后端慢、连接中断和停机,验证业务延迟、遥测缺口和恢复时间。优先 Java Agent 建立覆盖基线,再只为业务关键操作补手工 Span;升级语义约定时比较 span name、kind、属性和基数,避免仪表盘因字段漂移静默断裂。
门禁同时检查信号存在、语义正确、关联完整、基数受控、敏感字段缺失和故障时可恢复。围绕“Span 边界表达远程或可行动操作,父子与 Link 保持因果,采样和导出失败都有可计算缺口”记录 owner、预算、保留期、告警消费者和最近一次真实发现;没有消费者且不能回答诊断问题的信号应删除,而不是永久堆积。
