相关文章《Skywalking on the way-千亿级的数据储能、毫秒级的查询耗时》
欢迎关注公众号【架构染色】交流学习
一、背景
在上一篇《通过编码方式构建 SkyWalking 的 Trace-上篇》中介绍了为什么需要通过编码方式构建 SkyWalking Trace 的背景,并梳理了编程方式需要提供的能力合集。每一步都走的不顺利,昨天只摸索出了 EntrySpan 的构建方法,和部分的 ExitSpan 能力,今天时间有限,加上也依然有点曲折,所以今天就变成了中篇,好了基本情况同步完毕,开始正题。
二、所需能力梳理
满足 IM 埋点的功能,需要通过显示的编码来实现以下功能:
-
EntrySpan
-
如何创建 EntrySpan,指定名称 -
如何通过 ref 关联上游服务,即 Extract(Build the reference between this segment and a cross-process segment) -
如何打 Tag -
如何写 Log -
如何关闭 span -
ExitSpan
-
如何创建 ExitSpan -
如何指定 Peer -
如何打 Tag -
如何写 Log -
如何关闭 span -
如何构建 carrier 信息,传递给下游服务,由下游通过 Inject carrier 信息后,Segment 内部就完成了 ref 的构建 -
跨线程传递 Trace 信息
-
离开线程时:如何捕获当前线程的 Trace 上下文 -
进入线程后:如何将其他线程的 Trace 上下文注入到当前线程
先给结论:今天精力有限已调研好的能力只有第 2 部分,参加日更活动,下班后趁着热乎赶紧将今天学习的内容整理出来;为了满足一些同学能够快速使用这个能力的需求,下边先给结论,再讲过程和原理。对于着急使用的同学可快速得到帮助;本篇介绍 ExitSpan 的创建,以及跨进程 ExitSpan 和 EntrySpan 之间 ref 如何关联。第 3 部分放到下一篇介绍。
三、目标
先了解一下本篇模拟的场景和构建的trace
效果,否则示例代码看起来很懵。
3.1 目标
目标是构建这样两个Segment
-
每个 Segment
中有一个EntrySpan
,一个ExitSpan
-
第一个 Segment
的ExitSpan-1
对应第二个Segment
的EntrySpan-2
四、编码方式构建
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 示例效果
如果就是为了找个示例使用,那么看到这里就可以了,效果图你看出问题没(因为第一个 Segment 没有等第二个 Segment 就直接关闭了)?后边内容是介绍为什么会有上边的示例代码,笔者在探索验证过程中遇到了哪些问题。
5. 补充 Segment 的关联设计
1) 一个问题
一个Trace
中所有Segment
的 TID 是相同的,即通过 TID 把相关的Segment
串联起来,请想象一下,通过 TID 把相关Segment
罗列出来之后,这些Segment
之间的先后顺序是怎样,如何绘制请求在Segment
的Span
之间游走的轨迹呢?
2) ref 的关联设计
-
下游服务 Segment
中的EntrySpan
总是与上游服务Segment
中的某个ExitSpan
关联 -
下游服务 Segment
中的处理的请求源自上游Segment
,如EntrySpan-2
对应于ExitSpan-2
-
对下游服务 Segment
来说 ref 关系满足请求的一入一出原则;对上游服务Segment
来说满足一出多入(0..n)出原则 -
新 Segment
内EntrySpan
的parentSpanId
是上游Segment
对应ExitSpan
的spanId
,即EntrySpan-2
的parentSpanId
是ExitSpan-2
的spanId
,此处很重要。
六、小心别踩坑
6.1 EntrySpan
的 extract
无法使用 ExitSpan
所inject
的 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
中就拿不到ExitSpan
的trace
上下文。
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