ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Temporal Server 基于 OpenTelemetry 的链路追踪(Tracing)配置与实践指南

Temporal Server 基于 OpenTelemetry 的链路追踪(Tracing)配置与实践指南 后端工作流自动化任务调度【免费下载链接】temporalTemporal service项目地址https://gitcode.com/gh_mirrors/te/temporal点击查看免费下载本篇技术指南围绕 Temporal Server 的分布式链路追踪能力展开Temporal 服务端基于 Go 版 OpenTelemetryOTELSDK 完成埋点与遥测数据导出支持通过 YAML 配置或环境变量两种方式创建 OTLP/gRPC 追踪导出器exporter并内置了对 gRPC 调用的自动插桩。读完本文你将掌握如何在本地快速启用 Tracing 并接入 Grafana Tempo 查看工作流调用链、如何把追踪数据发往 OTEL Collector 或 Honeycomb 等托管后端、如何在服务端代码中正确地创建 Span 与传播追踪上下文以及遵循仓库内推荐的埋点规范。概述Temporal 的 Tracing 架构Temporal Server 使用 Go OpenTelemetry 库 完成插桩与多协议、多模型的遥测导出。在服务端进程中插桩代码Span 的创建与结束像日志语句一样内联在正常的服务处理代码中而导出器的配置则是在进程启动阶段统一执行并装配的。两者职责分离构成了 Temporal Tracing 的两条主线导出Exporting把生成的 Span 数据通过 exporter 送往可观测性后端见 common/telemetry/config.go 与 common/telemetry/env.go插桩Instrumenting在服务处理路径中创建、结束 Span 并传播上下文见 common/telemetry/grpc.go。关于 Tracing 的完整概念体系Span、Trace、上下文传播等本文不做展开可参考 OpenTelemetry 官方的 Traces 概念文档、第三方介绍 以及 OTEL 规范。快速开始本地一条命令跑通 TracingTemporal 仓库的开发环境已经内置了 Grafana Tempo 作为本地追踪后端最快只需三步运行make start-dependencies该命令会通过 Docker Compose 启动依赖服务其中包含 Grafana Tempo具体定义见 develop/docker-compose/docker-compose.yml 中的tempo服务使用make OTELtrue start或其他任意start-x命令启动服务端。当OTELtrue时Makefile 会自动导出一组环境变量OTEL_BSP_SCHEDULE_DELAY100BatchSpanProcessor 的批处理调度延迟设为 100ms加快本地 Span 上送OTEL_EXPORTER_OTLP_TRACES_INSECUREtrueOTLP 导出使用非 TLS 连接OTEL_TRACES_EXPORTERotlp启用 OTLP 追踪导出器TEMPORAL_OTEL_DEBUGtrue开启调试模式会额外记录请求/响应负载等详细信息见后文TEMPORAL_TEST_DATA_ENCODINGjson测试数据编码使用 JSON。浏览器访问 http://localhost:3000/explore在数据源下拉框中选择 Tempo 即可查询追踪数据。提示使用 TraceQL 查询语法{ .temporalWorkflowID ~ WF-ID.* }可以快速定位某个工作流的追踪数据。该属性来自服务端在 Span 上注入的工作流标签见 common/telemetry/tags.go 中的WorkflowIDKey。在 Linux 上Tempo、Grafana 等依赖以network_mode: host方式运行见 develop/docker-compose/docker-compose.linux.yml因此 OTLP gRPC 端口4317直接暴露在本机macOS/Windows 下则由 Docker 端口映射暴露4317见 docker-compose.darwin.yml。Tempo 的接收端配置在 develop/docker-compose/grafana/provisioning/tempo/tempo.yamlGrafana 的 Tempo 数据源预配置见 develop/docker-compose/grafana/provisioning/datasources/prometheus.yml。导出器配置理解 signal / model / protocol 三元组默认情况下Temporal 不会配置任何追踪导出器——不额外配置就不采集、不发送任何追踪数据。在 OpenTelemetry 中exporter 是一个抽象概念其具体实现由三个值组成的三元组决定signal信号traces、metrics 或 logs 之一本文只涉及 tracesmodel模型被导出 Span/Trace 数据所遵循的抽象数据模型protocol协议为上述模型指定的具体应用层协议绑定。Temporal 目前确认支持以otlp over grpc的方式导出追踪数据。这一点可以在源码中得到印证exporter的 YAML 反序列化逻辑common/telemetry/config.go只接受tracesotlpgrpc以及别名traceotlpgrpc和metricsotlpgrpc两种组合其余组合会直接返回unsupported exporter kind错误。通过配置文件YAML创建导出器服务端支持一个otelYAML 配置节用于配置一组进程级process-wide的导出器。最常见的场景是把追踪数据发送到本地运行的 agent如 OTEL Collector。在任意配置 YAML 文件中加入以下配置节即可otel: exporters: - kind: signal: traces model: otlp protocol: grpc spec: connection: insecure: true endpoint: localhost:4317另一个典型场景是直接把追踪数据发往 Honeycomb 的托管 OTLP 采集服务。这需要先从 Honeycomb 获取一个 API key然后使用如下配置otel: exporters: - kind: signal: traces model: otlp protocol: grpc spec: connection: endpoint: api.honeycomb.io:443 headers: x-honeycomb-team: a honeycomb API key多个导出器配置解析器支持通过重复提供kind/spec声明来定义多个导出器。例如 common/telemetry/config_test.go 展示了一个共享连接配置的示例其中connections节定义了一个名为conn1的 gRPC 连接随后 traces 与 metrics 两个导出器通过connection_name: conn1复用它otel: connections: - kind: grpc metadata: name: conn1 spec: endpoint: localhost:4317 exporters: - kind: signal: traces model: otlp protocol: grpc spec: connection_name: conn1 - kind: signal: metrics model: otlp protocol: grpc spec: connection_name: conn1spec 支持的完整配置字段除了文档提到的字段更多配置项可以在 common/telemetry/config_test.go 中找到。结合 common/telemetry/config.go 中otlpGrpcExporter的结构体定义spec下实际支持的字段如下字段类型说明connection_namestring引用connections节中预定义的命名 gRPC 连接为空时按connection内联配置建连connection.endpointstringOTLP gRPC 服务端地址如localhost:4317connection.insecurebool是否使用非 TLS 传输开发环境通常为trueconnection.blockbool是否阻塞等待连接建立对应 gRPC 的WithBlockconnection.user_agentstring自定义 gRPC User-Agentconnection.authoritystring设置 gRPC authority对应WithAuthorityconnection.read_buffer_sizeintgRPC 读缓冲大小默认 32KBconnection.write_buffer_sizeintgRPC 写缓冲大小默认 32KBconnection.connect_params.min_connect_timeoutduration最小连接超时默认 10sconnection.connect_params.backoffobject连接退避参数base_delay、multiplier、jitter、max_delay默认取自 gRPC backoff 默认配置headersmap[string]string附加到每次导出请求的 HTTP/gRPC 头如认证用的x-honeycomb-teamtimeoutduration导出超时默认 10sretry.enabledbool是否启用导出重试默认trueretry.initial_intervalduration重试初始间隔默认 5sretry.max_intervalduration重试最大间隔默认 30sretry.max_elapsed_timeduration重试总耗时上限默认 1 分钟这些默认值均取自 gRPC v1.46 与 OTEL v1.7 的文档见 common/telemetry/config.go。在解析阶段retry与timeout等参数会透传给otlptracegrpc的导出选项见buildOtlpGrpcSpanExportercommon/telemetry/config.go。需要说明的是上述大部分字段都是对底层 gRPC 客户端配置重试、超时等的透传日常使用通常只需关心endpoint、insecure、headers与timeout。通过环境变量配置导出器除了 YAMLOTEL Span 导出器也可以完全通过环境变量创建与配置。创建导出器OTEL_TRACES_EXPORTER环境变量用于创建 Span 导出器OTEL_TRACES_EXPORTERotlp在源码层面该变量由telemetry.SpanExportersFromEnvcommon/telemetry/env.go解析目前仅支持otlp值none会被忽略其他值会报unsupported OTEL exporter错误并且如果同时设置了OTEL_EXPORTER_OTLP_TRACES_PROTOCOL只接受grpc其他协议值会直接报错。注意如果配置文件里已经定义了 traces 导出器环境变量不会再额外创建导出器。不过两路来源会进行合并且环境变量的优先级更高。从 temporal/fx.go 的TraceExportModule可以看到合并逻辑配置文件导出的 exporter 与SpanExportersFromEnv返回的 exporter 以类型为 key 合入同一张 map后写入的env 来源会覆盖先写入的config 来源注释也明确写着 env overrides config。配置导出器Go OTEL SDK 还会读取一组规范的 OTEL 环境变量来完成导出器的配置。如果你更愿意用环境变量而非 YAML可以直接使用 OTEL 规范定义的变量例如OTEL_SERVICE_NAMEmy-service OTEL_EXPORTER_OTLP_TRACES_INSECUREtrue其中OTEL_SERVICE_NAME会作为资源属性注入 Span。在 Temporal 中服务名最终形如io.temporal.service如io.temporal.history、io.temporal.frontend且internal-frontend会被映射为frontend以便统一追踪视图也可以通过OTEL_SERVICE_NAME自定义前缀——见telemetry.ResourceServiceNamecommon/telemetry/env.go与资源装配逻辑temporal/fx.go。注意当环境变量与 YAML 提供的配置冲突时环境变量优先。在代码中插桩Tracer 与 TracerProvider 的管理导出器配置在进程启动阶段执行而插桩代码Span 的创建与结束则内联在正常的服务处理代码中。Span 由go.opentelemetry.io/otel/trace.Tracer对象创建Tracer由go.opentelemetry.io/otel/trace.TracerProvider实例创建。TracerProvider绑定到单个逻辑服务logical service因此一个 Temporal 进程最多会有四个这样的实例——分别对应 worker、matching、history、frontend 四个服务。而Tracer对象绑定到单个逻辑库logical library这与服务是两个不同的概念一个 history服务实例可能会执行来自 temporal common 库、gRPC 库和 gocql 库的代码。Tracer和TracerProvider的对象管理已经加入服务端的 fx DI 配置中见 temporal/fx.go 的ServiceTracingModule其可覆盖类型列表在 temporal/fx.go因此它们可以作为依赖注入到任何启用 fx 的对象构造函数中。由于单个进程内可能存在多个服务共存coresidentTemporal刻意不使用OTEL 库提供的单一全局TracerProvider能力而是让每个服务持有自己的实例。默认情况下gRPC 客户端与服务端都会通过开源的 otelgrpc 库自动插桩。Temporal 在 common/telemetry/grpc.go 中基于它封装了自定义的ServerStatsHandler/ClientStatsHandler并用temporalWorkflowID、temporalRunID、worker_task.id等属性对 Span 做增强标注见 common/telemetry/tags.go。开启调试模式TEMPORAL_OTEL_DEBUGtrue后服务端还会把请求/响应的 protobuf 负载、header、deadline 等细节写入 Span 属性便于本地排查见 common/telemetry/grpc.go。插桩实践建议遵循 OTEL 属性命名规范OpenTelemetry 项目发布了非规范性的 属性命名指南。至少要做到两点在创建自定义属性前先检查 semconv 中是否有合适的现成属性Temporal 自定义属性一律使用io.temporal前缀。仓库中的实际属性定义common/telemetry/tags.go正是这一规范的体现工作流相关属性如temporalWorkflowID、temporalRunID、temporalBusinessID带有temporal前缀组件类属性如persistence、queue.timer、queue.transfer、update.registry等则按语义归入io.temporal命名空间下的具体组件。在语义合适的包内创建共享属性键不要在 common 里为所有属性建一个大杂烩文件不要仅仅为了放 OTEL 属性而单独建包要在语义合适的包内定义一组attribute.Key并按需复用来构造attribute.KeyValue要提供一组工具函数把高频使用的聚合类型Tasks、WorkflowExecutions、TaskQueues 等转换为[]attribute.KeyValue。这样既能显著减少把attribute.KeyValue关联到trace.Span时所需的冗长代码也能通过共享同一个映射函数获得一致性收益。在 common 或其他非服务专属代码中启动 Span问题common 库代码可能被任何服务调用如何在 common 代码中启动一个绑定到正确服务frontend/history/matching/worker的 Span答案创建当前活跃 Span 的TracerProvider可以从该 Span 本身获取而当前活跃 Span 可以从context.Context中取得。示例// DoFoo is a function in the common package func DoFoo(ctx context.Context, x int, y string) string { var span trace.Span ctx, span trace.SpanFromContext(ctx).TracerProvider().Tracer(go.temporal.io/server/common).Start(DoFoo) defer span.End() return fmt.Sprintf(%v-%v, y, x) }Tracer的命名参数如go.temporal.io/server/common即上文提到的逻辑库标识它独立于服务维度因此这段代码无论运行在哪个服务进程中Span 都会落到当前服务对应的TracerProvider上。RecordError并不代表 Span 失败调用Span.RecordError是个好习惯但并非所有 error 都意味着失败。因此如果你既想记录错误、又想标记 Span 失败必须额外调用Span.SetStatus(codes.Error, err.Error())。可以考虑封装一个FailSpanWithError之类的工具函数来统一处理。在函数调用之外传播 TraceContextgRPC 调用默认由 otelgrpc 拦截器完成上下文传播。但在 goroutine 之间、或context.Context无法传递的地方如经由 Go channel 交接、或写入外部数据存储需要自行传播追踪信息。根据场景有两种做法对象不落外部存储例如放进 Go channel 但不会刷盘到数据库的对象可以从当前trace.Span中取出trace.SpanContexttrace.SpanContextFromContext(context.Context)或Span.SpanContext()随数据一起传递消费方用trace.ContextWithSpanContext(trace.SpanContext)恢复追踪状态。追踪状态需要序列化使用 OTEL 的 propagation 包把追踪状态转换为更易序列化的类型如map[string]string。propagation.TraceContext类型可以向键值对对象注入Inject或从中提取Extract追踪状态carrier : propagation.MapCarrier(map[string]string{}) propagation.TraceContext{}.Inject(ctx, carrier) // write the carrier object to a durable store为批量处理的每个任务单独建 SpanOpenTelemetry 的 Span 可以通过Link链接形成非父子关系。一个典型场景是批量处理例如一次数据库读取填充了大批工作项为每个工作项创建独立的 Span再把这些 Span 链接回批处理 Span而不是让它们成为批处理 Span 的逻辑子 Span。这样既能保留这批工作由谁产生的关联信息又不会让单个批处理 Span 承担过多子 Span。还想记录日志使用Span.AddEvent写入与 Span 关联的消息。按 OTEL 官方文档的说法An event is a human-readable message on a span that represents something happening during its lifetimeEvent 是 Span 上的人类可读消息表示其生命周期内发生的某件事。小结与排查指引启用链路本地开发用make OTELtrue start配合 Grafana Tempohttp://localhost:3000/explore生产环境在配置 YAML 中加入otel.exporters节或设置OTEL_TRACES_EXPORTERotlp等环境变量配置来源优先级自定义 exporter测试注入 环境变量 配置文件且冲突时环境变量优先于 YAML调试手段设置TEMPORAL_OTEL_DEBUGtrue可让 gRPC Span 携带请求/响应负载等详情方便定位问题更多细节完整的字段支持以 common/telemetry/config_test.go 与 common/telemetry/config.go 为准服务装配逻辑可查阅 temporal/fx.go 中的TraceExportModule与ServiceTracingModule。赞分享后端工作流自动化任务调度【免费下载链接】temporalTemporal service项目地址https://gitcode.com/gh_mirrors/te/temporal点击查看免费下载相关推荐StarRocks 分布式 Tracing 实战基于 OpenTelemetry 与 Jaeger 追踪 FE/BE 全链路StarRocks 分布式 Tracing 实战基于 OpenTelemetry 与 Jaeger 追踪 FE/BE 全链路 本篇聚焦 StarRocks 的数据库OLAP数据仓库大数据湖仓一体数据分析基于 OpenTelemetry 的 Agent 全链路追踪实战解读 mcp-agent 的 Agent Tracing 示例基于 OpenTelemetry 的 Agent 全链路追踪实战解读 mcp agent 的 Agent Tracing 示例 在构建多工具、多服务器的 MC人工智能AI AgentAgent 框架MCP ClientsAgent 工作流WCA竞赛标准配置csTimer的官方赛事模式全解析WCA竞赛标准配置csTimer的官方赛事模式全解析 csTimer作为专业的魔方计时训练工具不仅提供日常练习功能更内置符合世界魔方协会WCA标准的赛上一篇Qwen3-32B-GGUF创意写作应用角色扮演、故事生成与内容创作的AI助手下一篇Carnice-9b核心功能解析终端操作、文件编辑与多轮工具调用全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表