开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 7 天,点击查看活动详情
笔者曾基于 SkyWalking 打造千亿级储能的链路追踪系统
详情查看《Skywalking on the way-千亿级的数据储能、毫秒级的查询耗时》
欢迎关注公众号【架构染色】交流学习
一、需求背景:
随着自研的 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 埋点的功能,需要通过显示的编码来实现以下功能:
-
EntrySpan
- 如何创建
EntrySpan
,指定名称 - 如何通过 ref 关联上游服务,即
Extract
(Build the reference between this segment and a cross-process segment) - 如何打 Tag
- 如何写 Log
- 如何关闭 span
- 如何创建
-
ExitSpan
- 如何创建 ExitSpan
- 如何指定 Peer
- 如何打 Tag
- 如何写 Log
- 如何关闭 span
-
跨线程传递 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();
所以,根据示例代码应该分三步写:
- 构建 carrier
- extract carrier,得到一个 spanContext
- 传入 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#extract
中 get()
为 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 整理出更多跟监控相关的文章,敬请期待。
最后说一句(请关注,莫错过)
如果这篇文章对您有帮助,或者有所启发的话,欢迎关注公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。