分布式追踪与指标
可用性:两个变体(Full / Tiny)均支持。
OnePath 内置分布式链路追踪(tracing)与指标(metrics)。二者共享同一份消息附件 (attachment,OnePath TLV 编码),不影响用户 payload,完全通过环境变量开关,用户代码 无需任何修改即可启用。二者在运行时常一起使用:trace 描绘单条消息的跨跳链路, metrics 汇聚系统级的吞吐与耗时分布。
一、设计要点
- 非侵入:仅借助消息附件承载追踪信息,不影响 payload 与正常收发。
- 附件与 payload 分离:追踪信息与用户附件、payload 互不干扰;你照常使用 attachment 携带业务数据。
- 全部在 OnePath 层:追踪与指标完全由 OnePath 实现,对上层应用透明。
- TLV 编码 + 向后兼容:attachment 以 magic
0xD1 0x07+ version0x01开头;接收端 遇到非 OnePath TLV 的附件时,自动将其整体视为纯用户附件——因此与未启用 OnePath 追踪的 对端零侵入互通。 - 环境变量开关:用户不改代码即可启用 / 关闭所有功能。
二、环境变量速查
完整说明见 环境变量。
trace(5 个)
| 变量 | 默认 | 作用 |
|---|---|---|
ONEPATH_TRACE_ENABLE | 0 | tracing 总开关;设为 1 启用 |
ONEPATH_TRACE_SAMPLE_RATIO | 0.0 | 头部采样率 [0.0, 1.0];1.0 = 全采 |
ONEPATH_TRACE_NDJSON_PATH | (空) | NDJSON 导出路径;空 = 禁用文件导出 |
ONEPATH_TRACE_SERVICE_NAME | "onepath" | resource 属性 service.name |
ONEPATH_TRACE_AUTO_INJECT | 1 | 自动把追踪信息注入到 attachment;0 = 仅透传用户附件 |
metrics(3 个)
| 变量 | 默认 | 作用 |
|---|---|---|
ONEPATH_METRICS_ENABLE | 0 | metrics 总开关;设为 1 启用 |
ONEPATH_METRICS_NDJSON_PATH | (空) | metrics NDJSON 路径 |
ONEPATH_METRICS_FLUSH_MS | 5000 | metrics 异步刷盘间隔(毫秒) |
所有变量在 onepath_trace_init() / onepath_metrics_init() 首次调用时读取一次并缓存。 若用户代码不显式调用 init,首次 pub/sub/get 会触发 lazy 初始化(读取环境变量),保证 「不改代码即可启用」。之后修改环境变量无效(已缓存);要重新读取须先 onepath_trace_shutdown() 再 onepath_trace_init(NULL)。
三、快速启用
零代码修改,仅设环境变量:
# 进程 A: 数据采集端
ONEPATH_TRACE_ENABLE=1 \
ONEPATH_TRACE_SAMPLE_RATIO=1.0 \
ONEPATH_TRACE_NDJSON_PATH=/tmp/sensor.ndjson \
ONEPATH_METRICS_ENABLE=1 \
ONEPATH_METRICS_NDJSON_PATH=/tmp/sensor.metrics.ndjson \
./your_app sensor
# 进程 B: 转发 / 处理端
ONEPATH_TRACE_ENABLE=1 \
ONEPATH_TRACE_SAMPLE_RATIO=1.0 \
ONEPATH_TRACE_NDJSON_PATH=/tmp/router.ndjson \
./your_app router跑完后用 jq 看链路:
jq -s 'group_by(.trace_id) | map({
trace: .[0].trace_id,
hops: [.[] | {svc: .resource["service.name"], span: .name, kind: .kind, dur_ms: (.duration_ns/1e6)}]
})' /tmp/*.ndjson四、自动埋点覆盖范围
下列函数无需用户调用任何 trace API,会自动起 PRODUCER / CLIENT span 并把 traceparent 注入 attachment;接收端自动解 attachment 起 child span:
| 通路 | 注入端(起 span) | 提取端(起 child) | span 类型 |
|---|---|---|---|
| pub → sub | onepath_put, onepath_put_str, onepath_put_with_opts, onepath_publisher_put, onepath_publisher_put_str, onepath_publisher_write | 订阅回调(含端到端耗时) | PRODUCER → CONSUMER |
| get → reply | onepath_get, onepath_requester_get | 应答器回调 | CLIENT → SERVER |
| reply → 原客户端 | onepath_request_reply(注入 attachment) | onepath_reply_recv / onepath_reply_try_recv(含端到端耗时) | (SERVER 续) → CONSUMER |
拉取模式暂未埋点
拉取模式 onepath_sample_recv / onepath_sample_try_recv 暂未埋点:span 生命周期无法跨 用户处理逻辑。需要追踪请用回调模式(onepath_subscribe)。
五、追踪信息在 attachment 中的布局(OnePath TLV)
追踪信息以 OnePath 自定义的 TLV 结构编码进消息附件;这是 OnePath 在附件层之上新增的格式, 不影响用户自己的附件内容。
+--------+--------+--------+--------+--------+--------+--------+--------+
| magic 0xD1 0x07 | version 0x01 | <TLV items ...> |
+--------+--------+--------+--------+--------+--------+--------+--------+
每个 TLV item: [tag:1][len:N][value:len]
TAG_TRACEPARENT (0x01) 26 字节 W3C traceparent 二进制
TAG_TRACESTATE (0x02) (保留)
TAG_SEND_TS_NS (0x11) 8 字节大端 uint64 (发送时刻, ns)
TAG_USER (0x10) 用户原始附件字节- 发送时:OnePath 把当前活跃 span 的 traceparent + 发送时间戳 + 用户原始附件拼成上述 TLV,写入消息附件。
- 接收时:OnePath 从 TLV 取出 traceparent 作为父上下文起 child span,用 send_ts 计算 端到端耗时,并把
TAG_USER内容作为用户附件原样返回给你。
接收端遇到非 OnePath TLV(magic 不匹配)的附件时,会把整段附件视为纯用户附件,从而与 未启用 OnePath 追踪的对端零侵入互通。
六、多跳链路透传(转发场景)
6.1 关键设计:调用上下文自动透传
OnePath 在每个线程内维护一个 span 调用栈。只要中继进程也用 OnePath,不需要任何特殊 代码,trace_id 自动跨跳透传。
[sensor 进程] [中继进程] [storage 进程]
publisher_put(msg) 订阅回调(msg) 订阅回调(msg)
│ │ │
├─ 起 PRODUCER span (栈顶) ├─ 解 TLV → 父上下文 (sensor's) ├─ 解 TLV → 父上下文 (中继's)
├─ TLV 注入 traceparent ├─ span_start(CONSUMER, ├─ span_start(CONSUMER,
│ (trace_id=T, span_id=A) │ parent=ctx) → 压栈 │ parent=ctx) → 压栈
└─ put 出去 ├─ user_cb(...) ├─ user_cb(...)
│ └─ onepath_forward() │ (用户业务逻辑)
│ 或 publisher_put │
│ └─ 起 PRODUCER child │
│ (trace_id=T 不变, │
│ span_id=B) │
│ └─ TLV 注入新 traceparent
│ └─ put 出去
└─ span_end(CONSUMER) 出栈6.2 一行式转发助手
onepath_forward() 是这种模式的语法糖:
static void router_cb(onepath_sample_t *sample, void *userdata) {
onepath_publisher_t next_hop = (onepath_publisher_t)userdata;
/* 一行完成转发: payload + 用户附件透传, PRODUCER child span 自动起 */
onepath_forward(sample, next_hop);
onepath_sample_release(sample);
}onepath_forward 仅适合在订阅回调或应答器回调内调用——此时线程调用栈顶为活跃 CONSUMER/SERVER span。它会:
- 取当前线程调用栈顶的活跃 span(中继进程的 CONSUMER)作 parent。
- 起 PRODUCER child span(trace_id 不变,span_id 变化)。
- 把 child 的 traceparent + 新 send_ts + 用户附件拼成 TLV,注入到新消息的附件。
payload 与用户附件透传,编码沿用 publisher 默认值。返回 ONEPATH_OK 成功。
6.3 异步 / 跨线程场景
自动透传仅在同步回调内有效。跨线程 / 异步队列需要用显式 Span API (onepath_span_start 会自动把新 span 压入当前线程调用栈,onepath_span_end 自动出栈)。 具体函数签名与上下文结构见 链路追踪 API。
七、内置指标语义
ONEPATH_METRICS_ENABLE=1 时,以下指标在首次 pub/sub 时 lazy 注册:
| 名称 | 类型 | 含义 |
|---|---|---|
onepath.msgs.sent | counter | 成功发送的消息数 |
onepath.msgs.recv | counter | 接收到的消息数 |
onepath.msgs.dropped | counter | 发送失败丢弃数 |
onepath.bytes.sent | counter | 成功发送的字节数 |
onepath.bytes.recv | counter | 接收到的字节数 |
onepath.publish.latency | histogram | 发布路径耗时分布(μs) |
onepath.e2e.latency | histogram | 端到端跨跳耗时分布(μs,依赖时钟同步) |
直方图分桶:32 个对数桶,bucket[i] = [2^i, 2^(i+1)) us(即 1, 2, 4, …, 2^31 μs)。
八、限制与注意事项
时钟同步
跨节点端到端耗时(onepath.e2e.latency 及 span 内 send_ts / recv_ts)依赖系统实时时钟。 生产环境务必同集群、同步钟(NTP / PTP),否则负耗时会被丢弃。
- 调用上下文与异步:自动透传仅在同步回调内有效;异步需用显式 Span API(见 §6.3)。
- NDJSON 性能:高频 trace 下文件 I/O 是瓶颈。
- 无独立转发进程:OnePath 不提供独立的转发可执行文件,仅提供
onepath_forward助手; 用户自行拼装「订阅 +onepath_forward+ 发布」。 - 采样率:
SAMPLE_RATIO=0.0时仅维护调用栈,不导出 span;1.0时全采。 - 跨节点部署:两个变体均会自动选择合适的组播网络接口进行节点发现,跨机部署通常 无需额外配置;多网卡环境下如需指定特定网卡,可通过相应的网络接口环境变量覆盖。
- 性能预期:
*_ENABLE=0时所有 span / register 退化为 no-op,零开销;TRACE_ENABLE=1+SAMPLE_RATIO=0.0仅维护调用栈,吞吐下降 < 5%;SAMPLE_RATIO=1.0+ NDJSON 导出含文件 I/O,吞吐下降 < 30%。
API 参考
Span/Trace 函数签名见 链路追踪 API;指标注册与快照见 指标 API。
配套示例
配套示例程序 onepath_tracing_demo 演示三进程端到端链路:sensor → router → storage, 启动后各自输出 NDJSON,三份记录中可看到同一 trace_id 串联起三跳 span。