通过编码方式构建SkyWalking 的Trace-上篇

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
应用实时监控服务-用户体验监控,每月100OCU免费额度
简介: 通过编码方式构建SkyWalking 的Trace-上篇

一、需求背景:

随着自研的 Native 端 SkyWalking Agent 落地后,其能力、稳定性逐渐被肯定,隔壁移动部门的核心 WZ 业务开启了全面接入,链路信息覆盖了界面切换、用户操作以及请求交互,实时、精准的 Trace 数据让同事的日常巡检和问题排查变轻松了许多,杂事少了想法就多了,这不正准备将追踪能力应用到 IM 系统上,以便加强对 IM 数据的检测,提升 IM 问题的响应速度。

在去年有做过 IM 服务端的插件,但由于历史原因,服务端的线程特别多,一个请求就要二十多个 Segment。当时SkyWalking系统还未调优,IM 的数据量太大,另外服务端也有重构的规划,就没有上线。这次需配合移动端把链路补齐,考虑到定制插件不通用、IM 系统自身会迭代、插件的调试更新较麻烦等几个关键因素,IM 的负责人更期望通过显式的编码构建 Trace,以满足其快速实现、快速响应的目标。

二、寻找方案

印象之中,去年在整理 SkyWalking 的插件套件的时,似乎skywalking-agent\activations中有一个插件具有这个能力,翻看之后,找到了它apm-toolkit-opentracing-activation,从文档中找到一些简单的介绍:

  • Dependency the toolkit, such as using maven or gradle
<dependency>
      <groupId>org.apache.skywalking</groupId>
      <artifactId>apm-toolkit-opentracing</artifactId>
      <version>{project.release.version}</version>
   </dependency>
复制代码
  • Use our OpenTracing tracer implementation
Tracer tracer = new SkywalkingTracer();
Tracer.SpanBuilder spanBuilder = tracer.buildSpan("/yourApplication/yourService");
复制代码

内容有点少,再从百度翻查了几页,也只是看到简单的介绍“通过代码方式对 trace 信息进行补充”,而其功能详解和使用示例方面则没有收获。

不过有源码就不担心,因为 SkyWalking 的源码中给的注释还是很清晰的,于是开始探索验证。

三、所需能力梳理

满足 IM 埋点的功能,需要通过显示的编码来实现以下功能:

  1. EntrySpan
  • 如何创建EntrySpan,指定名称
  • 如何通过 ref 关联上游服务,即Extract(Build the reference between this segment and a cross-process segment)
  • 如何打 Tag
  • 如何写 Log
  • 如何关闭 span
  1. ExitSpan
  • 如何创建 ExitSpan
  • 如何指定 Peer
  • 如何打 Tag
  • 如何写 Log
  • 如何关闭 span
  1. 跨线程传递 Trace 信息
  • 离开线程时:如何捕获当前线程的 Trace 上下文
  • 进入线程后:如何将其他线程的 Trace 上下文注入到当前线程

先给结论:今天已调研好的能力只有 1 和 2 的一部分,因为参加日更活动,还需在下班后把今天学习的内容整理出来;所以就计划分成上下两篇来介绍,而且为了满足一些同学能够快速使用这个能力的需求,下边先给结论,再讲过程和原理。对于着急使用的同学可快速得到帮助;本篇就先介绍如何创建能关联上游服务的 EntrySpan

四、编码方式创建EntrySpan

4.1 添加依赖

依赖的版本请根据自家的情况填写

<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-opentracing</artifactId>
    <version>xxx</version>
</dependency>
复制代码

4.2 一个有效的示例

private static void makeEntryTrace(){
    Tracer tracer = new SkywalkingTracer();
    //1. 构建EntrySpan
    ActiveSpan activeSpan = tracer.buildSpan("/ReceiveMessage")
            .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)// 注意必须设置为entrySpan,才算是EntrySpan
            .startActive();
    //2. 构建 Carrier 并 通过 extract 注入到 当前Segment 的 ref
    //注意 注意 注意 这个代码必须要 构建EntrySpan之后。
    Map map = new HashMap();
    String sw8 = "1-OTUxNjNlODUxNGVjNGJmZDgwZDI2MmNmZjZiYzNiZWEuMTAzLjE2Njk2MjYxMzc4MTQwMTEx-OTUxNjNlODUxNGVjNGJmZDgwZDI2MmNmZjZiYzNiZWEuMTAzLjE2Njk2MjYxMzc4MTQwMDAw-1-c2t5d2Fsa2luZy1kZW1v-MTI3LjAuMC4x-L2R1YmJv-MTI3LjAuMC4xOjIwODgw";
    map.put("sw8",sw8);//先只构建一个sw8,其他两个暂不处理
    TextMap headerCarrier = new TextMapExtractAdapter(map);
    tracer.extract(
            Format.Builtin.TEXT_MAP,
            headerCarrier);
    //设置标签
    activeSpan.setTag("a","a");
    activeSpan.setTag("b","b");
    activeSpan.setTag("c","c");
    //当前EntrySpan中写日志
    activeSpan.log("log 1");
    //给当前EntrySpan指定名称,有些情况需要这种在运行到一定阶段后,才能从某个变量中获得值,用于构建span的名称
    activeSpan.setOperationName("/ReceiveMessage");//重新指定名称
    //关闭span,因为只有一个EntrySpan,则会关闭整个Segment,上报给OAP
    activeSpan.deactivate();
    System.out.println("成功关闭EntrySpan,Segment已上报可通过 TID : 95163e8514ec4bfd80d262cff6bc3bea.103.16696261378140111 查询");
}
复制代码

如果就是为了找个示例使用,那么看到这里就可以了,因一些不可抗拒的原因无法截图,所以没提供。后边内容是介绍为什么会有上边的示例代码,笔者在探索验证过程中遇到了哪些问题。

五、小心别踩坑

5.1 Sample values 只是示例,可不能用

1)错误示范

官网找个sw8的 Sample values,于是代码如下:

String sw8 = "1-TRACEID-SEGMENTID-3-PARENT_SERVICE-PARENT_INSTANCE-PARENT_ENDPOINT-IPPORT"
map.put("sw8","xxx");//先只构建一个sw8,其他两个暂不处理
复制代码

2)错误原因

org.apache.skywalking.apm.agent.core.context.ContextManager#extract中校验这个值不合法。

public static void extract(ContextCarrier carrier) {
    if (carrier == null) {
        throw new IllegalArgumentException("ContextCarrier can't be null.");
    }
    //注意上边的Sample值,这里校验不通过。
    if (carrier.isValid()) {
        get().extract(carrier);
    }
}
复制代码

3)老老实实找个正确的值,这里提供一个示例

String sw8 = "1-OTUxNjNlODUxNGVjNGJmZDgwZDI2MmNmZjZiYzNiZWEuMTAzLjE2Njk2MjYxMzc4MTQwMTEx-OTUxNjNlODUxNGVjNGJmZDgwZDI2MmNmZjZiYzNiZWEuMTAzLjE2Njk2MjYxMzc4MTQwMDAw-1-c2t5d2Fsa2luZy1kZW1v-MTI3LjAuMC4x-L2R1YmJv-MTI3LjAuMC4xOjIwODgw";
复制代码

5.2 extract 会开玩笑

1)错误示范

io.opentracing.Tracer#extract的注释给出 Example, 指引如下

Example:
       Tracer tracer = ...
       TextMap httpHeadersCarrier = new AnHttpHeaderCarrier(httpRequest);
       SpanContext spanCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, httpHeadersCarrier);
       ... = tracer.buildSpan('...').asChildOf(spanCtx).startActive();
复制代码

所以,根据示例代码应该分三步写:

  1. 构建 carrier
  2. extract carrier,得到一个 spanContext
  3. 传入 spanContext 创建 span

错误代码如下

//1. 构建 Carrier
Map map = new HashMap();
map.put("sw8","xxx");//先只构建一个sw8,其他两个暂不处理
TextMap headerCarrier = new TextMapExtractAdapter(map);
//2. 并 通过 extract 注入到 当前Segment 的 ref
SpanContext extract = tracer.extract(
        Format.Builtin.TEXT_MAP,
        headerCarrier);
//3. 构建EntrySpan
ActiveSpan activeSpan = tracer.buildSpan("/ReceiveMessage")
        .asChildOf(extract)
        .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)// 注意必须设置为entrySpan,才算是EntrySpan
        .startActive();
复制代码

2)错误原因

org.apache.skywalking.apm.agent.core.context.ContextManager#extractget()为 null。

public static void extract(ContextCarrier carrier) {
    if (carrier == null) {
        throw new IllegalArgumentException("ContextCarrier can't be null.");
    }
    if (carrier.isValid()) {
        // get() 为null
        get().extract(carrier);
    }
}
复制代码

为啥 get()为 null,因为org.apache.skywalking.apm.agent.core.context.ContextManager中的CONTEXT还是空的,为啥是空的呢?因为CONTEXT上下文都还没初始化。

private static AbstractTracerContext get() {
    return CONTEXT.get();
}
复制代码

3)通过创建span,先初始化CONTEXT

//1. 先构建EntrySpan
    ActiveSpan activeSpan = tracer.buildSpan("/ReceiveMessage")
            .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)// 注意必须设置为entrySpan,才算是EntrySpan
            .startActive();
复制代码

其他问题还有一些,毕竟忙活了大半天,但笔者感觉有些问题消耗精力是因为缺少引导资料,这里就不叨扰大家了。

六、总结

SkyWalking 主打非侵入式的 Agent 探针能力,从网络中没有顺利的找到自主编程式构建 Trace 的资料,还不确定这种方法是不被推荐、不常用或宣传不到位的哪种原因。当然 Java Agent 方式也是笔者很推崇的,因之前经历过基于 Cat 这种侵入式的埋点,当 Agent 能力需要升级时,要推动业务方配合升级是很麻烦的。但本次遇到的服务无通用性且自身有常规迭代的场景中,似乎用户自主通过编程 API 构建 Trace 信息,倒显得很合适。

本篇的内容看似简单,实际需要对 SkyWalking Agent 的工作机制、源码以及调试技巧等较为熟悉,否则遇到这些问题会无从下手,后边笔者会陆续基于 SkyWalking 整理出更多跟监控相关的文章,敬请期待。


相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
6月前
|
存储 Go
Go 浅析主流日志库:从设计层学习如何集成日志轮转与切割功能
本文将探讨几个热门的 go 日志库如 logrus、zap 和官网的 slog,我将分析这些库的的关键设计元素,探讨它们是如何支持日志轮转与切割功能的配置。
281 0
Go 浅析主流日志库:从设计层学习如何集成日志轮转与切割功能
|
前端开发 Java API
skywalking番外01 - 如何扩展%tid的logback占位符
skywalking番外01 - 如何扩展%tid的logback占位符
354 0
|
3月前
|
Java 应用服务中间件 HSF
Java应用结构规范问题之配置Logback以在控制台输出日志的问题如何解决
Java应用结构规范问题之配置Logback以在控制台输出日志的问题如何解决
|
5月前
|
SQL NoSQL Linux
gRPC 基础编码使用手册
gRPC 基础编码使用手册
68 6
|
6月前
|
消息中间件 存储 SQL
Flume【基础知识 01】简介 + 基本架构及核心概念 + 架构模式 + Agent内部原理 + 配置格式(一篇即可入门Flume)
【2月更文挑战第18天】Flume【基础知识 01】简介 + 基本架构及核心概念 + 架构模式 + Agent内部原理 + 配置格式(一篇即可入门Flume)
1801 0
|
Kubernetes Java Linux
轻量级日志系统Loki原理简介和使用(3)
轻量级日志系统Loki原理简介和使用(3)
952 0
轻量级日志系统Loki原理简介和使用(3)
|
6月前
|
Dubbo Java 应用服务中间件
微服务框架(十七)Dubbo协议及编码过程源码解析
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo协议、线程模型、和其基于Netty的NIO异步通讯机制及源码
|
监控 Java API
通过编码方式构建SkyWalking 的Trace-中篇
通过编码方式构建SkyWalking 的Trace-中篇
491 0
通过编码方式构建SkyWalking 的Trace-中篇
|
人工智能 监控 Java
SpringBoot实战(十六):集成Skywalking调用链监控系统
SpringBoot实战(十六):集成Skywalking调用链监控系统
777 0
|
存储
SkyWalking 中如何构建异步链路的 Trace
SkyWalking 中如何构建异步链路的 Trace
2768 1
SkyWalking 中如何构建异步链路的 Trace