OpenTelemetry J*a:利用上下文传播构建分布式 Span 关系


OpenTelemetry Java:利用上下文传播构建分布式 Span 关系

本文详细阐述了在 opentelemetry j*a 中如何基于 span id 实现分布式追踪的上下文传播。重点介绍了 opentelemetry 不直接通过 span id 获取 span 对象的设计理念,而是通过注入(inject)和提取(extract)操作,将追踪上下文(包括父 span id 和 trace id)在服务间传递,从而正确建立父子 span 关系,确保分布式系统中追踪链的完整性。

在分布式系统中,服务间的调用链路追踪是实现可观测性的关键。OpenTelemetry 提供了一套强大的 API 和 SDK 来实现这一目标。开发者在使用 OpenTelemetry J*a 进行追踪时,有时会遇到一个常见问题:如何在一个服务中,仅凭父 Span 的 ID 来“获取”或“引用”该父 Span,以便为当前操作设置正确的父子关系?

OpenTelemetry 的设计哲学是,Span 对象是特定执行上下文的瞬时表示。它不提供一个全局注册表或方法,允许用户通过 Span ID 直接检索一个 Span 对象。这是因为 Span 的生命周期通常与它所代表的操作绑定,并且在操作完成后即结束。当涉及到跨进程或跨服务边界的追踪时,OpenTelemetry 依赖于上下文传播(Context Propagation)机制来传递追踪信息,而非直接传递 Span 对象本身。

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

上下文传播是 OpenTelemetry 中处理分布式追踪父子关系的核心机制。它允许追踪上下文(SpanContext,其中包含 Trace ID 和 Span ID)在不同的服务或进程之间传递。这个过程通常分为两个主要步骤:

  1. 注入 (Inject):在调用方服务中,将当前 Span 的 SpanContext 序列化并注入到一个传输载体(如 HTTP 请求头、消息队列的元数据)中。
  2. 提取 (Extract):在被调用方服务中,从接收到的传输载体中反序列化出 SpanContext,并基于此上下文创建新的子 Span。

通过这种方式,即使父 Span 对象在调用方已经结束,其关键的追踪信息(Trace ID 和 Span ID)也能被传递到下游服务,从而在下游服务中正确地建立起与上游服务的父子关系,形成完整的分布式追踪链。

OpenTelemetry J*a 中的上下文传播实现

OpenTelemetry J*a SDK 提供了 TextMapPropagator 接口来处理上下文的注入和提取。W3C Trace Context 是推荐的跨服务追踪上下文传播标准,OpenTelemetry 默认支持并推荐使用 W3CTraceContextPropagator。

1. 注入(Injection):在调用方服务中传递上下文

在调用方服务中,您需要获取当前活跃的 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.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import j*a.util.HashMap;
import j*a.util.Map;

public class CallerService {

    private final Tracer tracer;
    private final OpenTelemetry openTelemetry;

    public CallerService(OpenTelemetry openTelemetry) {
        this.openTelemetry = openTelemetry;
        this.tracer = openTelemetry.getTracer("my-caller-service");
    }

    /**
     * 模拟一个远程调用,并将当前 Span 的上下文注入到传输载体中。
     * @return 包含追踪上下文的 Map,模拟 HTTP Headers。
     */
    public Map<String, String> makeRemoteCall() {
        Span parentSpan = tracer.spanBuilder("parentSpanInCaller")
            .startSpan();
        Map<String, String> carrier = new HashMap<>(); // 模拟 HTTP Headers 或消息队列元数据

        try (Scope scope = parentSpan.makeCurrent()) {
            // 获取当前上下文,并使用 W3CTraceContextPropagator 将其注入到 carrier 中
            W3CTraceContextPropagator.getInstance().inject(
                Context.current(), // 获取当前活跃的 Span 所在的上下文
                carrier,           // 传输载体,例如一个 Map 来模拟 HTTP 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("调用方 Span ID: " + parentSpan.getSpanContext().getSpanId());
            System.out.println("注入的追踪上下文: " + carrier);
        } finally {
            parentSpan.end();
        }
        return carrier;
    }
}

在上述代码中:

AVCLabs *CLabs

AI移除视频背景,100%自动和免费

AVCLabs 337 查看详情 AVCLabs
  • 我们创建了一个名为 parentSpanInCaller 的 Span,并将其设置为当前活跃 Span。
  • W3CTraceContextPropagator.getInstance().inject() 方法负责从 Context.current() 中提取 SpanContext 信息,并将其写入到 carrier(这里是一个 Map)中。TextMapSetter 接口定义了如何将键值对写入到载体中。
  • carrier 随后会被发送到被调用方服务。

2. 提取(Extraction):在被调用方服务中重建上下文

在被调用方服务中,您需要从接收到的请求中提取追踪上下文,并用它来作为新创建 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.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.TextMapGetter;
import j*a.util.Collections;
import j*a.util.Map;

public class CalleeService {

    private final Tracer tracer;
    private final OpenTelemetry openTelemetry;

    public CalleeService(OpenTelemetry openTelemetry) {
        this.openTelemetry = openTelemetry;
        this.tracer = openTelemetry.getTracer("my-callee-service");
    }

    /**
     * 模拟处理远程调用,从传输载体中提取上下文,并创建子 Span。
     * @param carrier 包含追踪上下文的 Map,模拟 HTTP Headers。
     */
    public void processRemoteCall(Map<String, String> carrier) {
        // 从 carrier 中提取追踪上下文
        Context extractedContext = W3CTraceContextPropagator.getInstance().extract(
            Context.current(), // 默认上下文,如果 carrier 中没有追踪信息,则使用此上下文
            carrier,           // 传输载体
            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);
                }
            });

        // 使用提取到的上下文作为新 Span 的父级
        Span childSpan = tracer.spanBuilder("childSpanInCallee")
            .setParent(extractedContext) // 将提取到的上下文设置为父级
            .startSpan();

        try {
            System.out.println("被调用方 Span ID: " + childSpan.getSpanContext().getSpanId());
            System.out.println("被调用方父 Span ID: " + childSpan.getParentSpanContext().getSpanId());
            // 模拟一些工作
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            childSpan.end();
        }
    }
}

在上述代码中:

  • W3CTraceContextPropagator.getInstance().extract() 方法从 carrier 中读取追踪上下文信息,并返回一个 Context 对象。TextMapGetter 接口定义了如何从载体中读取键值对。
  • 这个 extractedContext 对象包含了上游服务 Span 的 SpanContext。
  • 通过 tracer.spanBuilder("childSpanInCallee").setParent(extractedContext).startSpan(),我们创建了一个新的 Span,并明确指定其父级为 extractedContext 中携带的 Span。这样就成功建立了跨服务的父子关系。

完整的端到端示例

为了演示上述过程,我们需要一个主程序来初始化 OpenTelemetry SDK,并协调调用方和被调用方服务。

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.ConsoleSpanExporter;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;

import j*a.util.Map;

public class Main {
    public static void main(String[] args) {
        // 1. 配置 OpenTelemetry SDK
        // 定义服务资源,例如服务名称
        Resource serviceResource = Resource.getDefault()
            .toBuilder()
            .put(ResourceAttributes.SERVICE_NAME, "my-distributed-app")
            .build();

        // 配置 TracerProvider,使用 ConsoleSpanExporter 将 Span 输出到控制台
        // 并设置采样器为始终采样
        SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
            .addSpanProcessor(SimpleSpanProcessor.create(ConsoleSpanExporter.create()))
            .setResource(serviceResource)
            .setSampler(Sampler.alwaysOn())
            .build();

        // 构建并注册全局的 OpenTelemetry 实例
        OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
            .setTracerProvider(tracerProvider)
            // OpenTelemetry SDK 会自动注册 W3CTraceContextPropagator 作为默认的传播器
            // .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) // 显式设置传播器
            .buildAndRegisterGlobal();

        // 2. 初始化调用方和被调用方服务
        CallerService caller = new CallerService(openTelemetry);
        CalleeService callee = new CalleeService(openTelemetry);

        // 3. 模拟一次分布式调用
        System.out.println("--- 模拟远程调用开始 ---");
        Map<String, String> propagatedContext = caller.makeRemoteCall(); // 调用方注入上下文
        callee.processRemoteCall(propagatedContext); // 被调用方提取上下文并创建子 Span
        System.out.println("--- 远程调用模拟结束 ---");

        // 4. 关闭 TracerProvider 以确保所有 Span 都被导出
        tracerProvider.shutdown();
    }
}

运行此 Main 类,您将在控制台看到类似以下的输出(具体 Span ID 和 Trace ID 会有所不同):

--- 模拟远程调用开始 ---
调用方 Span ID: 6b3c...
注入的追踪上下文: {traceparent=00-d8b4...-6b3c...-01}
被调用方 Span ID: 7a8b...
被调用方父 Span ID: 6b3c...
--- 远程调用模拟结束 ---

并且,ConsoleSpanExporter 会将完整的 Span 数据打印出来,显示 childSpanInCallee 的 parentSpanId 正是 parentSpanInCaller 的 spanId,证明父子关系已成功建立。

注意事项与最佳实践

  1. 理解 SpanContext 与 Span 对象的区别

以上就是OpenTelemetry J*a:利用上下文传播构建分布式 Span 关系的详细内容,更多请关注其它相关文章!


# app  # java  # 山西seo优化软件  # 营销推广策划案新传  # 网站建设推广威昕hfqjwl  # 皇姑区推广网站建设公司  # 赵县雪花梨营销推广方案  # 机械设备市场推广营销  # 农安网站推广公司电话是多少  # 手机网站建设利用率高  # 安阳整站网站推广系统  # 潞州区营销短视频推广部  # 推荐使用  # 也能  # 主程序  # 序列化  # 是一个  # 并将其  # 配置文件  # 您需要  # 键值  # gate  # 键值对  # 常见问题  # 区别  # 注册表  # ai 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: Dash应用多值文本输入处理与类型转换教程  LocoySpider如何批量采集电商商品_LocoySpider电商采集的模板应用  word怎么将图片设置为页面背景并不影响打印_Word图片背景设置方法  Go语言中方法与接收器:指针和值类型的调用机制详解  LINUX怎么查看显卡信息_LINUX查看GPU状态  《图怪兽》退出登录方法  FullCalendar自定义按钮样式定制指南  什么是Satis,如何用它搭建一个私有的composer仓库?  《爱笔思画x》魔棒工具抠图教程  优化Asyncio嵌套函数调度:使用生产者-消费者模式实现并发流处理  win11怎么设置默认终端为Windows Terminal Win11替代CMD和PowerShell【技巧】  聚水潭ERP后台管理系统登录 聚水潭ERP官方登录通道  抖音如何解除|直播|权限绑定_抖音关闭并解绑|直播|功能的方法  Flexbox布局中Stencil组件宽度不显示问题解析与:host尺寸控制  QQ网站入口直接登录 QQ官方正版登录页面  HTML中多图片上传与预览:解决ID冲突的专业指南  HTML Canvas文本样式定制指南:解决外部字体加载与应用难题  123平台官方登录入口 123邮箱网页端在线沟通工具  高效调试PHP大型嵌套数组:JSON序列化与可视化工具实践  php如何实现多域名共享session_php存储session到redis与跨域读取配置  《sketchbook》选中部分图案移动方法  WooCommerce 新客户订单自动添加管理员备注教程  百度小说看书时如何翻页_百度小说手动翻页与自动翻页设置  曝《丝之歌》DLC有望开发!开发商还有神秘新企划  C++ bind函数使用教程_C++参数绑定与函数适配器的应用  无人机考证官网 中国民航无人机考证官网登录入口  铁路12306官网登录入口 铁路12306在线购票官方平台  Golang中的rune与byte类型区别是什么_Golang字符与字节处理详解  苹果SE如何开启单手模式_苹果SE单手操作功能  电脑双系统如何安装和卸载 Windows和Linux双系统安装教程【详解】  163邮箱登录入口官网 163.com邮箱登录入口  《伊瑟》凶影追缉库卢鲁boss攻略  英国搜索:多数英国人认为语言搜索是未来搜索  uc浏览器官网网页版使用 uc浏览器官网免费在线首页  高德地图导航路线偏差报警频繁怎么办 高德地图路线偏差修复与优化方法  汽水音乐车机版 汽水音乐车机版官方入口  微信客户端怎么查看二维码_微信客户端个人二维码查看方法  CSS布局中意外顶部空白的调试与解决:深入理解padding-top  《鹿路通》退余额方法  mysql通配符能用于日志查询吗_mysql通配符在系统日志查询中的实际使用方法  抖音手机分身两个账号怎么切换?分身两个系统是一样的吗?  mail.qq.com登录入口 QQ邮箱网页版直达  QQ邮箱官方登录页_腾讯出品安全稳定的邮箱服务  J*aScript调试技巧_性能分析与内存快照  偃武诸葛亮阵容搭配推荐  海棠阅读登录教程_详细讲解海棠登录操作  qq音乐官方网站入口_qq音乐在线听歌网页版链接  vivo手机视频通话美颜怎么设置_vivo视频通话美颜开启方法  PHP utf8_encode 字符编码转换疑难解析与最佳实践  PHP中实现JSON数据数组分页的教程 

 2025-12-05

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.