讲师中心 微信公众号
AI工具推荐 视频效率加速

OpenTelemetry Java:实现跨服务Span上下文传播

风萱同学_7472

风萱同学_7472

发布时间:2025-12-05 18:32:25

|

1033人浏览过

|

来源于php中文网

原创

OpenTelemetry Java:实现跨服务Span上下文传播

在opentelemetry java中,无法直接通过span id获取span对象。为了在分布式系统中建立父子span关系,应利用opentelemetry的上下文传播机制。通过`textmappropagator`将当前span的上下文注入到请求头等载体中,然后在接收端提取该上下文并作为新span的父级,从而正确地连接分布式跟踪链。

理解OpenTelemetry中的Span关联

在分布式系统中,服务间的调用需要建立明确的跟踪关系,即父子Span关系。OpenTelemetry通过Span来表示单个操作,并通过Context来传递这些Span之间的关系。一个常见的问题是,当一个服务接收到一个远程调用的请求,并且只知道父Span的ID时,是否能直接通过这个ID获取到父Span对象并将其设置为当前Span的父级。

答案是:不能直接通过Span ID获取Span对象。 OpenTelemetry的设计理念是轻量级和分布式,Span ID只是一个标识符,用于在分布式跟踪系统中唯一标识一个Span。它并非一个在内存中可直接引用的对象句柄,尤其是在跨进程或跨线程的场景下。一个Span对象通常只在其创建和活跃的本地进程中存在。

为了解决跨服务或跨进程的Span关联问题,OpenTelemetry引入了“上下文传播(Context Propagation)”机制。

核心概念:上下文传播(Context Propagation)

上下文传播是OpenTelemetry中实现分布式跟踪的关键。它允许将当前操作的跟踪上下文(包括Trace ID、Span ID以及其他跟踪状态)从一个服务传递到另一个服务,或者从一个异步操作传递到另一个。

立即学习“Java免费学习笔记(深入)”;

主要涉及以下两个核心组件:

  1. Context对象:OpenTelemetry的Context是一个不可变的数据结构,用于在代码执行路径中传递跟踪和度量信息。它包含了当前活动的Span以及其他与跟踪相关的状态。
  2. TextMapPropagator:这是一个接口,定义了如何将Context信息“注入”(inject)到请求头、消息队列元数据等文本格式的载体中,以及如何从这些载体中“提取”(extract)Context信息。OpenTelemetry支持多种传播协议,如W3C Trace Context、B3等。

实践指南:实现跨服务Span上下文传播

以下是如何在Java中使用OpenTelemetry实现上下文传播的详细步骤。

Java Maven Common
Java Maven Common

Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。

下载

1. 发送方:注入上下文

当服务A调用服务B时,服务A需要将当前活动的Span的上下文注入到出站请求中。

示例代码:

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.context.propagation.TextMapSetter;

import java.util.HashMap;
import java.util.Map;

public class SenderService {

    private final OpenTelemetry openTelemetry;
    private final Tracer tracer;

    public SenderService(OpenTelemetry openTelemetry) {
        this.openTelemetry = openTelemetry;
        this.tracer = openTelemetry.getTracer("SenderService");
    }

    public Map<String, String> makeRemoteCall(String serviceName) {
        // 1. 创建一个Span,并使其成为当前活动的Span
        Span span = tracer.spanBuilder("sendTo" + serviceName)
                .startSpan();

        Map<String, String> headers = new HashMap<>(); // 模拟HTTP请求头

        try (Scope scope = span.makeCurrent()) {
            // 2. 获取当前Context,其中包含活动的Span
            Context currentContext = Context.current();

            // 3. 使用TextMapPropagator将Context注入到headers中
            openTelemetry.getPropagators().getTextMapPropagator().inject(
                    currentContext,
                    headers,
                    new TextMapSetter<Map<String, String>>() {
                        @Override
                        public void set(Map<String, String> carrier, String key, String value) {
                            carrier.put(key, value);
                        }
                    });

            System.out.println("Sender Span ID: " + span.getSpanContext().getSpanId());
            System.out.println("Injected headers: " + headers);

            // 模拟执行一些业务逻辑
            // ...

        } finally {
            span.end(); // 结束Span
        }
        return headers;
    }

    public static void main(String[] args) {
        // 通常OpenTelemetry实例会在应用启动时初始化
        // 这里为了示例方便,简单创建
        OpenTelemetry openTelemetry = OpenTelemetry.noop(); // 实际应用中会使用SDK初始化

        SenderService sender = new SenderService(openTelemetry);
        sender.makeRemoteCall("ReceiverService");
    }
}

在上述代码中:

  • span.makeCurrent()将当前Span设置为活跃Span,并将其放入Context.current()中。
  • openTelemetry.getPropagators().getTextMapPropagator().inject()负责将Context.current()中的跟踪信息(如Trace ID和Span ID)序列化并写入到headers这个载体中。TextMapSetter定义了如何将键值对写入载体。

2. 接收方:提取上下文并创建子Span

当服务B接收到服务A的请求时,它需要从请求中提取上下文,并使用该上下文作为新创建Span的父级。

示例代码:

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.TextMapGetter;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ReceiverService {

    private final OpenTelemetry openTelemetry;
    private final Tracer tracer;

    public ReceiverService(OpenTelemetry openTelemetry) {
        this.openTelemetry = openTelemetry;
        this.tracer = openTelemetry.getTracer("ReceiverService");
    }

    public void processRemoteCall(Map<String, String> headers) {
        // 1. 从接收到的请求头中提取Context
        Context extractedContext = openTelemetry.getPropagators().getTextMapPropagator().extract(
                Context.current(), // 默认的当前Context,如果不存在父Context,则使用它
                headers,
                new TextMapGetter<Map<String, String>>() {
                    @Override
                    public Iterable<String> keys(Map<String, String> carrier) {
                        return carrier.keySet();
                    }

                    @Override
                    public String get(Map<String, String> carrier, String key) {
                        return carrier.get(key);
                    }
                });

        // 2. 将提取到的Context设置为新Span的父级
        // 注意:这里不是直接传入Span对象,而是传入包含父Span信息的Context
        Span span = tracer.spanBuilder("processRequest")
                .setParent(extractedContext) // 将提取到的Context作为父级
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            System.out.println("Receiver Span ID: " + span.getSpanContext().getSpanId());
            System.out.println("Receiver Parent Span ID (from Context): " + Span.fromContext(extractedContext).getSpanContext().getSpanId());
            // 模拟执行一些业务逻辑
            // ...
        } finally {
            span.end(); // 结束Span
        }
    }

    public static void main(String[] args) {
        OpenTelemetry openTelemetry = OpenTelemetry.noop(); // 实际应用中会使用SDK初始化

        // 模拟从SenderService接收到的headers
        Map<String, String> receivedHeaders = new HashMap<>();
        receivedHeaders.put("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"); // 示例W3C Trace Context头

        ReceiverService receiver = new ReceiverService(openTelemetry);
        receiver.processRemoteCall(receivedHeaders);
    }
}

在上述代码中:

  • openTelemetry.getPropagators().getTextMapPropagator().extract()从headers中读取跟踪信息,并将其解析成一个新的Context对象。TextMapGetter定义了如何从载体中获取键值对。
  • tracer.spanBuilder("processRequest").setParent(extractedContext).startSpan()创建了一个新的Span,并明确地将其父级设置为从远程请求中提取的extractedContext。这样,新的Span就会与远程的父Span关联起来,形成完整的跟踪链。

注意事项与最佳实践

  1. 统一传播协议:确保所有服务都使用相同的TextMapPropagator配置。OpenTelemetry默认推荐并支持W3C Trace Context标准,它能够跨语言和框架进行互操作。
  2. 避免直接传递Span对象:永远不要尝试序列化或反序列化Span对象,或者将其作为方法参数在不同进程间传递。Span对象是本地资源,其生命周期和状态管理由SDK负责。
  3. 理解Context的作用:Context是传递跟踪信息的正确方式。它是一个不可变且线程安全的容器,用于在代码执行流中传递隐式数据。
  4. 自动和手动埋点结合:对于常见的HTTP、gRPC、消息队列等,OpenTelemetry提供了大量的自动埋点(Auto-instrumentation),可以自动处理上下文传播。对于自定义协议或特定业务逻辑,才需要进行手动埋点。
  5. Span ID仅是标识符:再次强调,Span ID只是一个标识符,它不提供获取对应Span对象的API。跟踪系统的核心在于通过Context来构建和关联Span树。

总结

在OpenTelemetry Java中,直接通过Span ID获取Span对象是不可行的。要实现分布式系统中的父子Span关联,必须依赖于上下文传播(Context Propagation)机制。通过TextMapPropagator在发送方注入上下文到请求载体中,并在接收方从载体中提取上下文,然后使用提取到的Context作为新Span的父级,可以确保分布式跟踪链的正确连接。理解并正确应用这一机制是构建有效OpenTelemetry可观测性解决方案的关键。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

相关专题

更多
什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

2033

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

3074

2023.10.07

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

14340

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1103

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

8110

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

3202

2025.08.07

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2501

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

356

2025.12.22

OpenJDK JVM参数怎么调优
OpenJDK JVM参数怎么调优

Java应用运行缓慢,如何调整JVM参数?本专题介绍OpenJDK JVM性能调优的基本流程,涵盖性能指标确认、运行数据采集、内存配置和垃圾回收分析,帮助你先定位瓶颈,再通过对照测试验证效果,避免直接套用参数而影响应用稳定性。

20

2026.10.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn