SkyWalking 的Trace还能通过编码构建 -中篇

SkyWalking 的Trace还能通过编码构建 -中篇

相关文章《Skywalking on the way-千亿级的数据储能、毫秒级的查询耗时》

欢迎关注公众号【架构染色】交流学习

一、背景

在上一篇《通过编码方式构建 SkyWalking 的 Trace-上篇》中介绍了为什么需要通过编码方式构建 SkyWalking Trace 的背景,并梳理了编程方式需要提供的能力合集。每一步都走的不顺利,昨天只摸索出了 EntrySpan 的构建方法,和部分的 ExitSpan 能力,今天时间有限,加上也依然有点曲折,所以今天就变成了中篇,好了基本情况同步完毕,开始正题。

二、所需能力梳理

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

  1. EntrySpan

    • 如何创建 EntrySpan,指定名称
    • 如何通过 ref 关联上游服务,即 Extract(Build the reference between this segment and a cross-process segment)
    • 如何打 Tag
    • 如何写 Log
    • 如何关闭 span
  2. ExitSpan

    • 如何创建 ExitSpan
    • 如何指定 Peer
    • 如何打 Tag
    • 如何写 Log
    • 如何关闭 span
    • 如何构建 carrier 信息,传递给下游服务,由下游通过 Inject carrier 信息后,Segment 内部就完成了 ref 的构建
  3. 跨线程传递 Trace 信息

    • 离开线程时:如何捕获当前线程的 Trace 上下文
    • 进入线程后:如何将其他线程的 Trace 上下文注入到当前线程

先给结论:今天精力有限已调研好的能力只有第 2 部分,参加日更活动,下班后趁着热乎赶紧将今天学习的内容整理出来;为了满足一些同学能够快速使用这个能力的需求,下边先给结论,再讲过程和原理。对于着急使用的同学可快速得到帮助;本篇介绍 ExitSpan 的创建,以及跨进程 ExitSpan 和 EntrySpan 之间 ref 如何关联。第 3 部分放到下一篇介绍。

三、目标

先了解一下本篇模拟的场景和构建的trace效果,否则示例代码看起来很懵。

3.1 目标

目标是构建这样两个Segment

  • 每个Segment中有一个EntrySpan,一个ExitSpan
  • 第一个SegmentExitSpan-1对应第二个SegmentEntrySpan-2
SkyWalking 的Trace还能通过编码构建 -中篇
image.png

四、编码方式构建

4.1 添加依赖

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

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

4.2 一个有效的示例

import io.opentracing.ActiveSpan;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.propagation.TextMapExtractAdapter;
import io.opentracing.tag.Tags;
import org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer;

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

public class TracerTest {

    public static void main(String[] args) throws InterruptedException {
        TextMapInjectAdapterCustom textMap = makeEntryTrace();
        String sw8 = textMap.getSW8();
        makeEntryTrace2(textMap, sw8);
        Thread.sleep(40000);
    }

    private static TextMapInjectAdapterCustom makeExitTrace(Tracer tracer, String operationName) {
        //构建并激活ExitSpan
        Tracer.SpanBuilder spanBuilder = tracer.buildSpan(operationName);
        spanBuilder.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);//设置为Exit Span
        spanBuilder.withTag(Tags.PEER_HOST_IPV4.getKey(), "127.0.0.1");//必须设置peer,否则不是Exit Span
        ActiveSpan activeSpan = spanBuilder.startActive();
        //inject carrier,下游的EntrySpan要用这个carrier 构建 ref
        SpanContext exitSpanContext = activeSpan.context();
        TextMapInjectAdapterCustom headerCarrier = new TextMapInjectAdapterCustom(new HashMap());
        tracer.inject(exitSpanContext, Format.Builtin.TEXT_MAP, headerCarrier);
        //关闭ExitSpan
        activeSpan.close();
        return headerCarrier;
    }

    private static TextMapInjectAdapterCustom 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之后。
        String sw8 = "1-OTUxNjNlODUxNGVjNGJmZDgwZDI2MmNmZjZiYzNiZWEuMTAzLjE2Njk2MjYxMzc4MTQwMTE2-OTUxNjNlODUxNGVjNGJmZDgwZDI2MmNmZjZiYzNiZWEuMTAzLjE2Njk2MjYxMzc4MTQwMDAw-1-c2t5d2Fsa2luZy1kZW1v-MTI3LjAuMC4x-L2R1YmJv-MTI3LjAuMC4xOjIwODgw";
        Map map = new HashMap();
        map.put("sw8", sw8);//先只构建一个sw8,其他两个暂不处理
        TextMap headerCarrier = new TextMapExtractAdapter(map);
        tracer.extract(Format.Builtin.TEXT_MAP, headerCarrier);

        //设置标签
        activeSpan.setTag("a""a");
        //当前EntrySpan中写日志
        activeSpan.log("log 1");
        //给当前EntrySpan指定名称,有些情况需要这种在运行到一定阶段后,才能从某个变量中获得值,用于构建span的名称
        activeSpan.setOperationName("/ReceiveMessage");//重新指定名称
        // 创建ExitSpan,并返回ExitSpan中的Carrier,给下游的EntrySpan使用
        TextMapInjectAdapterCustom textMap = makeExitTrace(tracer, "/sendMessage");
        //关闭span,因为只有一个EntrySpan,则会关闭整个Segment,上报给OAP
        activeSpan.deactivate();
        return textMap;
    }


    private static void makeEntryTrace2(TextMapInjectAdapterCustom headerCarrier, String sw8) {
        Tracer tracer = new SkywalkingTracer();

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

        //2.1 构建 Carrier 并 通过 extract 注入到 当前Segment 的 ref
        // 关键在于这里,我扩展了 toTextMapExtractAdapterCustom()方法
        TextMapExtractAdapterCustom textMapExtractAdapterCustom = headerCarrier.toTextMapExtractAdapterCustom();
        tracer.extract(Format.Builtin.TEXT_MAP, textMapExtractAdapterCustom);

        //2.2 使用原生的sw8 K-V来构建,在 makeEntryTrace()中有示例,此处不在重复

        //创建ExitSpan,指定名称为“/sendMessage2"
        makeExitTrace(tracer, "
/sendMessage2");
        //关闭span,因为只有一个EntrySpan,则会关闭整个Segment,上报给OAP
        activeSpan.deactivate();
    }
}

自定义了一个TextMapInjectAdapterCustom

import io.opentracing.propagation.TextMap;

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

public final class TextMapInjectAdapterCustom implements TextMap {
    private final Map<String, String> map;

    public TextMapInjectAdapterCustom(final Map<String, String> map) {
        this.map = map;
    }

    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
    }

    @Override
    public void put(String key, String value) {
        this.map.put(key, value);
    }

    public TextMapExtractAdapterCustom toTextMapExtractAdapterCustom() {
        Map<String, String> extractMap = new HashMap<>();
        extractMap.putAll(this.map);
        TextMapExtractAdapterCustom textMapExtractAdapterCustom = new TextMapExtractAdapterCustom(extractMap);
        return textMapExtractAdapterCustom;
    }

    public String getSW8() {
        return map.get("sw8");
    }

}

4.3 示例效果

SkyWalking 的Trace还能通过编码构建 -中篇
image.png

如果就是为了找个示例使用,那么看到这里就可以了,效果图你看出问题没(因为第一个 Segment 没有等第二个 Segment 就直接关闭了)?后边内容是介绍为什么会有上边的示例代码,笔者在探索验证过程中遇到了哪些问题。

5. 补充 Segment 的关联设计

1) 一个问题

一个Trace中所有Segment的 TID 是相同的,即通过 TID 把相关的Segment串联起来,请想象一下,通过 TID 把相关Segment罗列出来之后,这些Segment之间的先后顺序是怎样,如何绘制请求在SegmentSpan之间游走的轨迹呢?

SkyWalking 的Trace还能通过编码构建 -中篇

2) ref 的关联设计

  • 下游服务Segment中的EntrySpan总是与上游服务Segment中的某个ExitSpan关联
  • 下游服务Segment中的处理的请求源自上游Segment,如EntrySpan-2对应于ExitSpan-2
  • 对下游服务Segment来说 ref 关系满足请求的一入一出原则;对上游服务Segment来说满足一出多入(0..n)出原则
  • SegmentEntrySpanparentSpanId 是上游Segment对应ExitSpanspanId,即EntrySpan-2parentSpanIdExitSpan-2spanId,此处很重要。

六、小心别踩坑

6.1 EntrySpanextract 无法使用 ExitSpaninject 的 carrier 信息.

1)错误示范

//---ExitSpan中--------
//在创建ExitSpan时,inject到  TextMapInjectAdapter 类型的headerCarrier示例,
TextMapInjectAdapter headerCarrier = new TextMapInjectAdapter(new HashMap());
tracer.inject(exitSpanContext, Format.Builtin.TEXT_MAP, headerCarrier);
...
//---EntrySpan中--------
//把这个TextMapInjectAdapter 类型的headerCarrier示例,给EntrySpan extract使用
tracer.extract(Format.Builtin.TEXT_MAP, headerCarrier);

2)错误提示

Exception in thread "main" java.lang.UnsupportedOperationException: TextMapInjectAdapter should only be used with Tracer.inject()
 at io.opentracing.propagation.TextMapInjectAdapter.iterator(TextMapInjectAdapter.java:39)
 at TracerTest.makeEntryTrace2(TracerTest.java:145)
 at TracerTest.main(TracerTest.java:26)

3)原因梳理 TextMapInjectAdapter#iterator 方法不让用,看下边代码

public final class TextMapInjectAdapter implements TextMap {
    private final Map<String,String> map;

    public TextMapInjectAdapterCustom(final Map<String,String> map) {
        this.map = map;
    }

    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
    }
}

这个封装中iterator不能用,就无法从 map 中读出来 carrier 信息,读不出来那EntrySpan中就拿不到ExitSpantrace上下文。

4)自己改造 自定一个TextMap的实现类,扩展读取的方法

  • toTextMapExtractAdapterCustom :直接转换并返回一个 EntrySpan 可以使用的对象
  • getSW8:返回核心的 sw8 的值,程序中拿到这个值之后,自己可以在线程之间传递。
public final class TextMapInjectAdapterCustom implements TextMap {
    private final Map<String, String> map;

    public TextMapInjectAdapterCustom(final Map<String, String> map) {
        this.map = map;
    }

    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
    }

    @Override
    public void put(String key, String value) {
        this.map.put(key, value);
    }

    public TextMapExtractAdapterCustom toTextMapExtractAdapterCustom() {
        Map<String, String> extractMap = new HashMap<>();
        extractMap.putAll(this.map);
        TextMapExtractAdapterCustom textMapExtractAdapterCustom = new TextMapExtractAdapterCustom(extractMap);
        return textMapExtractAdapterCustom;
    }

    public String getSW8() {
        return map.get("sw8");
    }
}

6.2 创建的Span不是ExitSpan类型

1)错误示范

//构建并激活ExitSpan
Tracer.SpanBuilder spanBuilder = tracer.buildSpan(operationName);
spanBuilder.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);//设置为Exit Span
ActiveSpan activeSpan = spanBuilder.startActive();

2)错误原因

org.apache.skywalking.apm.toolkit.activation.opentracing.span.ConstructorWithSpanBuilderInterceptor#onConstruct中有判断 peer 的情况,peer 为空的话,就按照 LocalSpan 类型创建

// peer 为空的话,就按照LocalSpan类型创建
else if (spanBuilder.isExit() && (!StringUtil.isEmpty(spanBuilder.getPeer()))) {
    span = ContextManager.createExitSpan(spanBuilder.getOperationName(), buildRemotePeer(spanBuilder));
else {
    span = ContextManager.createLocalSpan(spanBuilder.getOperationName());
}

3)指定 peer 即可

//构建并激活ExitSpan
Tracer.SpanBuilder spanBuilder = tracer.buildSpan(operationName);
spanBuilder.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);//设置为Exit Span
spanBuilder.withTag(Tags.PEER_HOST_IPV4.getKey(), "127.0.0.1");//必须设置peer,否则不是Exit Span
ActiveSpan activeSpan = spanBuilder.startActive();

其他问题还有一小些,这里就不叨扰大家了。

七、总结

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

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

最后说一句(请关注,莫错过)

如果这篇文章对您有帮助,或有所启发的话,欢迎关注公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

原文始发于微信公众号(架构染色):SkyWalking 的Trace还能通过编码构建 -中篇

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/65447.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!