zipkin

前言:ZIPKIN作为当前“分布式服务链路 跟踪”问题的流行解决方案之一,正在被越来越多的公司和个人学习使用。其中很重要的一块,就是上报链路数据。那么知道服务端如何接收数据,以及我们该怎样上报数据到服务端就显得十分重要。虽然ZIPKIN官方也开源了一个客户端Brave,但是本文却并不想直接介绍Brave,而是想站在一个从零开发ZIPKIN客户端的角度,一层层分析解决如何自己写一个ZIPKIN客户端,直到最后引出Brave。本文最终想达到的效果,就是希望通过本文,能让大家对ZIPKIN的链路上报有一个详尽的理解和认识。

零行代码快速开始

ZIPKIN是Spring Boot服务,因此启动起来十分方便,直接运行ZIPKIN JAR包就可以了。ZIPKIN JAR包我们可以自行编译ZIPKIN源码获得,也可以从下面的仓库获取,这个仓库专门存放ZIPKIN各种编译好的JAR包 ,仓库地址为:

https://dl.bintray.com/openzipkin/maven/io/zipkin/java/

进入zipkin-server目录下载server jar包,比如下载2.11.5版本,运行jar包:

java -jar zipkin-server-2.11.5-exec.jar

这样本地就起了一个ZIPKIN服务,浏览器中输入http://localhost:9411/zipkin/,即可打开ZIPKIN首页,效果如下:

接下来,我们开始上报数据,现在我不想写任何代码,那么就用postman发起一次post请求上报数据吧:

上报后在本地ZIPKIN服务上即可看到刚上报的数据效果:

没有任何一句代码,一个完整的ZIPKIN数据上报存储展示就完成了,那么我们来思考下:

1、如果自己写一个上ZIPKIN 客户端,该如何写?

分析:要上传数据给服务端,那么必须要搞清楚,ZIPKIN服务端可以接受什么样格式的数据?支持的编码解码协议是什么?还有支持那些通信传输协议?

Task: 了解了服务怎么接收数据后,客户端的task也就明确了:

  1. 以ZIPKIN服务端支持的数据格式组织数据;
  2. 以ZIPKIN服务端支持的编解码协议编码数据;
  3. 以ZIPKIN服务端支持的传输协议上报数据。

2、基础ZIPKIN客户端完成后,如何适配各种组件?

描述:一个服务通常涉及多个组件,包括服务框架,消息中间件 ,各种数据库等,那么怎么上报这些组件的数据给服务端了,值得我们思考?

分析:其实最简单的方法就是采用一个装饰者模式包装一下组件接口,在其中加入上报的逻辑,但是这种方法局限很大,不灵活。除此之外,我们或许我们可以利用下拦截器 技术,AOP技术,以及Agent,探针等技术做到无侵入上报数据。

服务端如何接收数据的?

要想自己写一个ZIPKIN客户端,必须搞清楚ZIPKIN服务端是怎么接收数据的,包括数据格式协议是怎样的?编解码协议是怎样的?还有支持那么传输协议?

ZIPKIN支持的数据格式是怎样的?

ZIPKIN是兼容OpenTracing标准的,OpenTracing规定,每个Span包含如下状态:

  • 操作名称
  • 起始时间
  • 结束时间
  • 一组 KV 值,作为阶段的标签(Span Tags)
  • 阶段日志 (Span Logs)
  • 阶段上下文(SpanContext),其中包含 Trace ID 和 Span ID
  • 引用关系(References)

OpenTracing规范与zipkin对应关系如下:

OpenTracingZIPKIN
操作名称name
起始时间timestamp
结束时间timestamp+duration
标签tags
Span上下文traceId
引用关系parentId
Span日志

除此之外,ZIPKIN SAPN还增加了其他几个字端:

字段描述
KindSpan类型,比如是Server还是Client
Annotaion表示某个时间点发生的Event,Event类型:cs:Client Send 请求;sr:Server Receive到请求;ss:Server 处理完成、并Send Response;cr:Client Receive 到响应
Endpoint localEndpoint描述本地服务信息,比如服务名,ip等,方便根据服务名 检索链路
Endpoint remoteEndpointRPC调用时,描述远程服务的服务信息

最终,包含兼容OpenTracing标准的字端,以及其本身的一些字端后,zipkin的span数据字端如下:

了解了ZIPKIN的数据字端格式后,我们再看看ZIPKIN支持的编解码协议。

ZIPKIN支持那些编解码协议?

Zipkin主要支持三种编解码协议,分别为JSON, PROTO3, THRIFT。

JSON编解码如下:

JSON_V1 {
    public Encoding encoding() {
        return Encoding.JSON;
    }
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decode</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">Span</span> <span class="n">result</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">decodeOne</span><span class="o">(</span><span class="n">bytes</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="n">out</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="o">(</span><span class="k">new</span> <span class="n">V1JsonSpanReader</span><span class="o">()).</span><span class="na">readList</span><span class="o">(</span><span class="n">spans</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="n">Span</span> <span class="nf">decodeOne</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">V1Span</span> <span class="n">v1</span> <span class="o">=</span> <span class="o">(</span><span class="n">V1Span</span><span class="o">)</span><span class="n">JsonCodec</span><span class="o">.</span><span class="na">readOne</span><span class="o">(</span><span class="k">new</span> <span class="n">V1JsonSpanReader</span><span class="o">(),</span> <span class="n">span</span><span class="o">);</span>
    <span class="n">List</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>
    <span class="n">V1SpanConverter</span><span class="o">.</span><span class="na">create</span><span class="o">().</span><span class="na">convert</span><span class="o">(</span><span class="n">v1</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
    <span class="k">return</span> <span class="o">(</span><span class="n">Span</span><span class="o">)</span><span class="n">out</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">0</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">decodeList</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">spans</span><span class="o">);</span>
<span class="o">}</span>

}

JSON_V2 {
public Encoding encoding() {
return Encoding.JSON;
}

<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decode</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">JsonCodec</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="k">new</span> <span class="n">V2SpanReader</span><span class="o">(),</span> <span class="n">span</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">JsonCodec</span><span class="o">.</span><span class="na">readList</span><span class="o">(</span><span class="k">new</span> <span class="n">V2SpanReader</span><span class="o">(),</span> <span class="n">spans</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="nd">@Nullable</span>
<span class="kd">public</span> <span class="n">Span</span> <span class="nf">decodeOne</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="o">(</span><span class="n">Span</span><span class="o">)</span><span class="n">JsonCodec</span><span class="o">.</span><span class="na">readOne</span><span class="o">(</span><span class="k">new</span> <span class="n">V2SpanReader</span><span class="o">(),</span> <span class="n">span</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">decodeList</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">spans</span><span class="o">);</span>
<span class="o">}</span>

}

THRIFT编解码如下:

THRIFT {
public Encoding encoding() {
return Encoding.THRIFT;
}
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decode</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">ThriftCodec</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">span</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">ThriftCodec</span><span class="o">.</span><span class="na">readList</span><span class="o">(</span><span class="n">spans</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="n">Span</span> <span class="nf">decodeOne</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">ThriftCodec</span><span class="o">.</span><span class="na">readOne</span><span class="o">(</span><span class="n">span</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">decodeList</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">spans</span><span class="o">);</span>
<span class="o">}</span>

}

PROTO3编解码如下:

PROTO3 {
public Encoding encoding() {
return Encoding.PROTO3;
}
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decode</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">Proto3Codec</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">span</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">,</span> <span class="n">Collection</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">Proto3Codec</span><span class="o">.</span><span class="na">readList</span><span class="o">(</span><span class="n">spans</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
<span class="o">}</span>

<span class="nd">@Nullable</span>
<span class="kd">public</span> <span class="n">Span</span> <span class="nf">decodeOne</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">Proto3Codec</span><span class="o">.</span><span class="na">readOne</span><span class="o">(</span><span class="n">span</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="nf">decodeList</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">spans</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">decodeList</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">spans</span><span class="o">);</span>
<span class="o">}</span>

}

其中Json是默认支持的,也是使用起来最方便的,除此之外,还包括Thirft和Proto3可供开发者选择。

ZIPKIN支持那些传输协议?

Zipkin默认支持Http协议,除此之外,它还支持kafka,rabbitmq以及scribe协议:

他们的初始化过程如下:

传输协议支持的编解码协议如下:

其中Scribe限定了只支持Thirft协议,而HTTP、Kafka和RabbitMQ则是三种协议都支持。

如何做到支持所有的编码解码协议 了?ZIPKIN中提供了一个自动探测编解码的类SpanBytesDecoderDetector,其中核心方法如下:

static BytesDecoder<Span> detectDecoder(byte[] bytes) {
if (bytes[0] <= 16) { // binary format
if (protobuf3(bytes)) return SpanBytesDecoder.PROTO3;
return SpanBytesDecoder.THRIFT; /* the first byte is the TType, in a range 0-16 */
} else if (bytes[0] != ‘[’ && bytes[0] != ‘{’) {
throw new IllegalArgumentException(“Could not detect the span format”);
}
if (contains(bytes, ENDPOINT_FIELD_SUFFIX)) return SpanBytesDecoder.JSON_V2;
if (contains(bytes, TAGS_FIELD)) return SpanBytesDecoder.JSON_V2;
return SpanBytesDecoder.JSON_V1;
}

现在我们已经知道了zipkin服务接收的数据格式以及编解码协议和传输协议,那么接下来就可以写一个客户端了!

自己写一个ZIPKIN客户端

对于服务端如何接收数据,有了一个全面的认识后,我们就可以着手开始写一个ZIPKIN客户端了。

那么,首先定义客户端上报的数据格式,最简单的方式就是定义一个跟ZIPKIN服务端一样数据格式的Span就可以了:

@Setter
@Getter
public static class MySapn {
private String traceId;
private String parentId;
private String id;
private String name;
private long timestamp;
private long duration;
private Map<String, String> tags;
String kind;
Endpoint localEndpoint, remoteEndpoint;
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">enum</span> <span class="n">Kind</span> <span class="o">{</span>
    <span class="n">CLIENT</span><span class="o">,</span>
    <span class="n">SERVER</span><span class="o">,</span>
    <span class="n">PRODUCER</span><span class="o">,</span>
    <span class="n">CONSUMER</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Endpoint</span> <span class="o">{</span>
    <span class="n">String</span> <span class="n">serviceName</span><span class="o">,</span> <span class="n">ipv4</span><span class="o">,</span> <span class="n">ipv6</span><span class="o">;</span>
    <span class="kt">byte</span><span class="o">[]</span> <span class="n">ipv4Bytes</span><span class="o">,</span> <span class="n">ipv6Bytes</span><span class="o">;</span>
    <span class="kt">int</span> <span class="n">port</span><span class="o">;</span> <span class="c1">// zero means null


public Endpoint(String serviceName) {
this.serviceName = serviceName;
}
}
}

数据格式确定后,接着就编码数据,ZIPKIN支持三种编码方式,JSON、THIFT和PROTO3,为了简单方便,我们选择JSON协议编码Span数据。注意,ZIPKIN JSON字符串前后需要加括号。

数据编码后,接着上报数据,ZIPKIN默认支持HTTP协议方式,JAVA HTTP请求包很多,我们随便选择一种,比如选择Apach的HttpClient jar包 ,代码如下:

public class App {
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">serverUrl</span> <span class="o">=</span> <span class="s">"http://localhost:9411/api/v2/spans"</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">MySapn</span> <span class="n">span</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MySapn</span><span class="o">();</span>
    <span class="n">span</span><span class="o">.</span><span class="na">traceId</span> <span class="o">=</span> <span class="s">"1ae1e4f435814744"</span><span class="o">;</span>
    <span class="n">span</span><span class="o">.</span><span class="na">parentId</span> <span class="o">=</span> <span class="s">"1ae1e4f435814744"</span><span class="o">;</span>
    <span class="n">span</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="s">"d1ab9cd2c50d13d1"</span><span class="o">;</span>
    <span class="n">span</span><span class="o">.</span><span class="na">kind</span> <span class="o">=</span> <span class="n">MySapn</span><span class="o">.</span><span class="na">Kind</span><span class="o">.</span><span class="na">SERVER</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
    <span class="n">span</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="s">"my client test"</span><span class="o">;</span>
    <span class="n">span</span><span class="o">.</span><span class="na">timestamp</span> <span class="o">=</span> <span class="n">1565933251470428L</span><span class="o">;</span>
    <span class="n">span</span><span class="o">.</span><span class="na">duration</span> <span class="o">=</span> <span class="n">8286</span><span class="o">;</span>
    <span class="n">span</span><span class="o">.</span><span class="na">localEndpoint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MySapn</span><span class="o">.</span><span class="na">Endpoint</span><span class="o">(</span><span class="s">"My client"</span><span class="o">);</span>
    <span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">tags</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HashMap</span><span class="o">&lt;&gt;();</span>
    <span class="n">tags</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="s">"pioneeryi"</span><span class="o">);</span>
    <span class="n">tags</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"lover"</span><span class="o">,</span> <span class="s">"dandan"</span><span class="o">);</span>
    <span class="n">span</span><span class="o">.</span><span class="na">tags</span> <span class="o">=</span> <span class="n">tags</span><span class="o">;</span>

    <span class="n">doPost</span><span class="o">(</span><span class="n">serverUrl</span><span class="o">,</span> <span class="n">span</span><span class="o">);</span>
    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Hello World!"</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">doPost</span><span class="o">(</span><span class="n">String</span> <span class="n">url</span><span class="o">,</span> <span class="n">MySapn</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">HttpClient</span> <span class="n">httpClient</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DefaultHttpClient</span><span class="o">();</span>

        <span class="n">HttpPost</span> <span class="n">post</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HttpPost</span><span class="o">(</span><span class="n">url</span><span class="o">);</span>
        <span class="n">post</span><span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"Content-Type"</span><span class="o">,</span> <span class="s">"application/json"</span><span class="o">);</span>
        <span class="n">post</span><span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"charset"</span><span class="o">,</span> <span class="s">"UTF-8"</span><span class="o">);</span>

        <span class="n">String</span> <span class="n">body</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Gson</span><span class="o">().</span><span class="na">toJson</span><span class="o">(</span><span class="n">span</span><span class="o">);</span>
        <span class="n">body</span> <span class="o">=</span> <span class="s">"["</span> <span class="o">+</span> <span class="n">body</span> <span class="o">+</span> <span class="s">"]"</span><span class="o">;</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">body</span><span class="o">);</span>

        <span class="n">StringEntity</span> <span class="n">entity</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StringEntity</span><span class="o">(</span><span class="n">body</span><span class="o">);</span>
        <span class="n">post</span><span class="o">.</span><span class="na">setEntity</span><span class="o">(</span><span class="n">entity</span><span class="o">);</span>

        <span class="n">HttpResponse</span> <span class="n">httpResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">post</span><span class="o">);</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">httpResponse</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">exception</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="s">"do post request fail"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

}

maven pom如下:

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>

运行上面main方法,即可上报Span数据,打开zipkin首页:http://localhost:9411/zipkin/,搜索刚上报的Span的TraceId,展示效果如下:

一个最简单的,采用JSON编码数据,HTTP协议上传数据的客户端我们就完成了。为了用户调用方便,我们可以将上面的代码封装为一个接口供用户使用:

public void reportSpan(MySpan span)

但是这个太简陋了,我们只支持了一种一个编解码协议,一种传输协议,开始优化:

优化一:支持多种编解码,支持多种传输协议。这个时候就需要定一个上报接口类了,根据不同传输协议提供不同实现。编解码也是同样的,定义编码接口,根据不同编码协议提供不同实现。

此外,当前这个客户端是同步上报的,性能很差,因此必须改成异步上报,接着优化。

优化二:上报改成异步上报,队列+线程。

有了这两步优化后,client大体框架就初步成型了,写好了客户端后,怎么适配各个组件,做到无侵入上报也很重要,继续优化:

优化三:适配各个组件,比如Spring Boot,Kafak,MySql等等。

一个完整的Client,还是有很多工作要做的,这里咱就不继续深入优化开发了,直接看看官方的Brave怎么做的!

探究ZIPKIN客户端Brave

为了说明Brave的使用和上报过程,我们先写一个很简单的上报Demo,进行演示。Demo将上报一个“一父Span,两个子Span“的链路,demo如下:

public class TraceDemo {
public static void main(String[] args) {
Sender sender = OkHttpSender.create(“http://localhost:9411/api/v2/spans”);
AsyncReporter asyncReporter = AsyncReporter.create(sender);
Tracing tracing = Tracing.newBuilder()
.localServiceName(“my-service”)
.spanReporter(asyncReporter)
.build();
Tracer tracer = tracing.tracer();
    <span class="n">Span</span> <span class="n">parentSpan</span> <span class="o">=</span> <span class="n">tracer</span><span class="o">.</span><span class="na">newTrace</span><span class="o">().</span><span class="na">name</span><span class="o">(</span><span class="s">"parent span"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
    
    <span class="n">Span</span> <span class="n">childSpan1</span> <span class="o">=</span> <span class="n">tracer</span><span class="o">.</span><span class="na">newChild</span><span class="o">(</span><span class="n">parentSpan</span><span class="o">.</span><span class="na">context</span><span class="o">()).</span><span class="na">name</span><span class="o">(</span><span class="s">"child span1"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
    <span class="n">sleep</span><span class="o">(</span><span class="n">500</span><span class="o">);</span>
    <span class="n">childSpan1</span><span class="o">.</span><span class="na">finish</span><span class="o">();</span>

    <span class="n">Span</span> <span class="n">childSpan2</span> <span class="o">=</span> <span class="n">tracer</span><span class="o">.</span><span class="na">newChild</span><span class="o">(</span><span class="n">parentSpan</span><span class="o">.</span><span class="na">context</span><span class="o">()).</span><span class="na">name</span><span class="o">(</span><span class="s">"child span2"</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
    <span class="n">sleep</span><span class="o">(</span><span class="n">500</span><span class="o">);</span>
    <span class="n">childSpan2</span><span class="o">.</span><span class="na">finish</span><span class="o">();</span>
    
    <span class="n">parentSpan</span><span class="o">.</span><span class="na">finish</span><span class="o">();</span>
<span class="o">}</span>

}

其中 maven pom 引入如下包:

<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>5.3.3</version>
</dependency>

<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>2.6.0</version>
</dependency>

启动zipkin 服务:

java -jar zipkin-server-2.11.5-exec.jar

然后在浏览器中输入:http://localhost:9411,即可打开zipkin首页,看到示例代码上报的效果图如下:

一个最简单的但是却是很完整的一个ZIPKIN链路上报演示,就如上面Demo所示,那么接下来分析一下整个链路的上报过程!

链路上报解析

自己开发一个客户端时,我们首先会封装一个Span类,Brave也不例外,它也定义了Span数据结构 ;那么定义好Span后,谁来负责构造Span了?

Brave中定义了一个类叫Tracer来完成构造Span的工作;Brave生成好了Span后,此时需要编码发送了,那么谁又来发送了?

Brave定义了Reporter组件,它支持异步发送以及多传输协议以及多编码协议发送Span数据。

看起来,各个组件已经完备了,组件有点多!此时需要那么一个人将这些组件组织起来,同时与服务端取得联系,开始打通整个流程了,这就是Tracing类 的功能。

接下来详细讲解一下各个组件的功能,并根据Demo代码,将各个组件串起来,最终梳理清楚Brave上报的流程和原理。

Brave Span

Brave中Span相关类有:SpanCustomizer、NoopSpanCustomizer、CurrentSpanCustomizer、RealSpanCustomizer、Span、NoopSpan、RealSpan。

示例代码中,关于span的操作如下:

Span span = tracer.newTrace().name(“encode”).start();
try {
doSomethingExpensive();
} finally {
span.finish();
}

首先通过tracer生成一个span,最后,调用span.finish()上报,接下来就来看看span,以及这个finish干了什么。

咱们用的Span的实现子类为RealSpan,RealSpan两个核心方法start, finish:

@Override
public Span start() {
return start(clock.currentTimeMicroseconds());
}

@Override
public Span start(long timestamp) {
synchronized (state) {
state.startTimestamp(timestamp);
}
return this;
}

start方法主要记录开始时间,接下来看看finish 方法:

@Override
public void finish(long timestamp) {
if (!pendingSpans.remove(context)) return;
synchronized (state) {
state.finishTimestamp(timestamp);
}
finishedSpanHandler.handle(context, state);
}

这里交给FinishedSpanHandler来处理。FinishedSpanHandler是一个抽象类 ,他有如下子类实现:

  • ZipkinFinishedSpanHandler
  • MetricsFinishedSpanHandler
  • NoopAwareFinishedSpan
  • CompositeFinishedSpanHandler

我们主要关注ZipkinFinishedSpanHandler实现:

public final class ZipkinFinishedSpanHandler extends FinishedSpanHandler {
final Reporter<zipkin2.Span> spanReporter;
final MutableSpanConverter converter;
<span class="kd">public</span> <span class="nf">ZipkinFinishedSpanHandler</span><span class="o">(</span><span class="n">Reporter</span><span class="o">&lt;</span><span class="n">zipkin2</span><span class="o">.</span><span class="na">Span</span><span class="o">&gt;</span> <span class="n">spanReporter</span><span class="o">,</span>
<span class="n">ErrorParser</span> <span class="n">errorParser</span><span class="o">,</span> <span class="n">String</span> <span class="n">serviceName</span><span class="o">,</span> <span class="n">String</span> <span class="n">ip</span><span class="o">,</span> <span class="kt">int</span> <span class="n">port</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">spanReporter</span> <span class="o">=</span> <span class="n">spanReporter</span><span class="o">;</span>
    <span class="k">this</span><span class="o">.</span><span class="na">converter</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MutableSpanConverter</span><span class="o">(</span><span class="n">errorParser</span><span class="o">,</span> <span class="n">serviceName</span><span class="o">,</span> <span class="n">ip</span><span class="o">,</span> <span class="n">port</span><span class="o">);</span>
<span class="o">}</span>

<span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">handle</span><span class="o">(</span><span class="n">TraceContext</span> <span class="n">context</span><span class="o">,</span> <span class="n">MutableSpan</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">Boolean</span><span class="o">.</span><span class="na">TRUE</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">sampled</span><span class="o">()))</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>

    <span class="n">Span</span><span class="o">.</span><span class="na">Builder</span> <span class="n">builderWithContextData</span> <span class="o">=</span> <span class="n">Span</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">()</span>
        <span class="o">.</span><span class="na">traceId</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">traceIdString</span><span class="o">())</span>
        <span class="o">.</span><span class="na">parentId</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">parentIdString</span><span class="o">())</span>
        <span class="o">.</span><span class="na">id</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">spanIdString</span><span class="o">());</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">debug</span><span class="o">())</span> <span class="n">builderWithContextData</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>

    <span class="n">converter</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span><span class="n">span</span><span class="o">,</span> <span class="n">builderWithContextData</span><span class="o">);</span>
    <span class="n">spanReporter</span><span class="o">.</span><span class="na">report</span><span class="o">(</span><span class="n">builderWithContextData</span><span class="o">.</span><span class="na">build</span><span class="o">());</span>
    <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>

<span class="nd">@Override</span> <span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">spanReporter</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span>

}

可见,上面最终是通过Reporter组件来上报数据的。那么Report是如何上报的了?

Brave Reporter

因为上报组件要支持多种编码协议以及多种传输协议,因此逻辑比较复杂,官方专门建了一个项目:zipkin-reporter-java

我们首先看看Reporter接口类 定义:

public interface Reporter<S> {
Reporter<Span> NOOP = new Reporter<Span>() {
@Override public void report(Span span) {
}
    <span class="nd">@Override</span> <span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="s">"NoopReporter{}"</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">};</span>
<span class="n">Reporter</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;</span> <span class="n">CONSOLE</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Reporter</span><span class="o">&lt;</span><span class="n">Span</span><span class="o">&gt;()</span> <span class="o">{</span>
    <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">report</span><span class="o">(</span><span class="n">Span</span> <span class="n">span</span><span class="o">)</span> <span class="o">{</span>
         <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">span</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="nd">@Override</span> <span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="s">"ConsoleReporter{}"</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">};</span>

<span class="cm">/**

* Schedules the span to be sent onto the transport .
*
* @param span Span, should not be <code>null</code>.
*/
void report(S span);
}

Reporter有三个子类实现,分别是CONSOLE,NOOP,和AsyncReporter。CONSOLE就是直接控制台打印出Span数据,一般用来调试差不多;NOOP是一个空实现,啥也不干;AsyncReporter是我们平时上报用的Reporter,他提供异步上报数据能力。

AsyncReporter

AsyncReporter is how you actually get spans to zipkin. By default, it waits up to a second before flushes any pending spans out of process via a Sender.

根据不同协议,Ayncreporter执行相应的build逻辑:

public AsyncReporter<Span> build() {
switch(this.sender.encoding()) {
case JSON:
return this.build(SpanBytesEncoder.JSON_V2);
case PROTO3:
return this.build(SpanBytesEncoder.PROTO3);
case THRIFT:
return this.build(SpanBytesEncoder.THRIFT);
default:
throw new UnsupportedOperationException(this.sender.encoding().name());
}
}

build方法详细逻辑,如下:

public <S> AsyncReporter<S> build(BytesEncoder<S> encoder) {
if (encoder == null) throw new NullPointerException(“encoder == null”);
<span class="k">if</span> <span class="o">(</span><span class="n">encoder</span><span class="o">.</span><span class="na">encoding</span><span class="o">()</span> <span class="o">!=</span> <span class="n">sender</span><span class="o">.</span><span class="na">encoding</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="n">IllegalArgumentException</span><span class="o">(</span><span class="n">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span>
<span class="s">"Encoder doesn't match Sender: %s %s"</span><span class="o">,</span> <span class="n">encoder</span><span class="o">.</span><span class="na">encoding</span><span class="o">(),</span> <span class="n">sender</span><span class="o">.</span><span class="na">encoding</span><span class="o">()));</span>
<span class="o">}</span>

<span class="kd">final</span> <span class="n">BoundedAsyncReporter</span><span class="o">&lt;</span><span class="n">S</span><span class="o">&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BoundedAsyncReporter</span><span class="o">&lt;&gt;(</span><span class="k">this</span><span class="o">,</span> <span class="n">encoder</span><span class="o">);</span>

<span class="k">if</span> <span class="o">(</span><span class="n">messageTimeoutNanos</span> <span class="o">&gt;</span> <span class="n">0</span><span class="o">)</span> <span class="o">{</span> 
    <span class="c1">// Start a thread that flushes the queue in a loop.

final BufferNextMessage<S> consumer =
BufferNextMessage.create(encoder.encoding(), messageMaxBytes, messageTimeoutNanos);
Thread flushThread = threadFactory.newThread(new Flusher<>(result, consumer));
flushThread.setName(“AsyncReporter{” + sender + “}”);
flushThread.setDaemon(true);
flushThread.start();
}
return result;
}

可以看到AsyncReporter的build方法中,启动了一个守护线程 flushThread,一直循环调用BoundedAsyncReporter的flush方法:

void flush(BufferNextMessage<S> bundler) {
if (closed.get()) throw new IllegalStateException(“closed”);
pending.drainTo(bundler, bundler.remainingNanos());
<span class="c1">// record after flushing reduces the amount of gauge events vs on doing this on report

metrics.updateQueuedSpans(pending.count);
metrics.updateQueuedBytes(pending.sizeInBytes);

<span class="c1">// loop around if we are running, and the bundle isn't full

// if we are closed, try to send what’s pending
if (!bundler.isReady() && !closed.get()) return;

<span class="c1">// Signal that we are about to send a message of a known size in bytes

metrics.incrementMessages();
metrics.incrementMessageBytes(bundler.sizeInBytes());
ArrayList<byte[]> nextMessage = new ArrayList<>(bundler.count());
bundler.drain(new SpanWithSizeConsumer<S>() {
@Override public boolean offer(S next, int nextSizeInBytes) {
nextMessage.add(encoder.encode(next)); // speculatively add to the pending message
if (sender.messageSizeInBytes(nextMessage) > messageMaxBytes) {
// if we overran the message size, remove the encoded message.
nextMessage.remove(nextMessage.size() - 1);
return false;
}
return true;
}
});

<span class="k">try</span> <span class="o">{</span>
    <span class="n">sender</span><span class="o">.</span><span class="na">sendSpans</span><span class="o">(</span><span class="n">nextMessage</span><span class="o">).</span><span class="na">execute</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="o">|</span> <span class="n">RuntimeException</span> <span class="o">|</span> <span class="n">Error</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
    <span class="o">......</span>
<span class="o">}</span>

}

Flush方法的主要逻辑如下:

  • 将队列pending中的数据,提取到nextMessage链表中;
  • 调用Sender的sendSpans方法,发送到nextMessage链表中的Span数据到Zipkin;

这样,Reporter即做到了异步发送!

Sender

Sender组件完成发送Span到zipkin服务端的最后一步,即利用某个传输协议,将数据发送到zipkin服务端

public abstract class Sender extends Component {
public Sender() {
}
<span class="kd">public</span> <span class="kd">abstract</span> <span class="n">Encoding</span> <span class="nf">encoding</span><span class="o">();</span>

<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">int</span> <span class="nf">messageMaxBytes</span><span class="o">();</span>

<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">int</span> <span class="nf">messageSizeInBytes</span><span class="o">(</span><span class="n">List</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[]&gt;</span> <span class="n">var1</span><span class="o">);</span>

<span class="kd">public</span> <span class="kt">int</span> <span class="nf">messageSizeInBytes</span><span class="o">(</span><span class="kt">int</span> <span class="n">encodedSizeInBytes</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">messageSizeInBytes</span><span class="o">(</span><span class="n">Collections</span><span class="o">.</span><span class="na">singletonList</span><span class="o">(</span><span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">encodedSizeInBytes</span><span class="o">]));</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">abstract</span> <span class="n">Call</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">sendSpans</span><span class="o">(</span><span class="n">List</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[]&gt;</span> <span class="n">var1</span><span class="o">);</span>

}

其中核心的方法sendSpans 是一个抽象方法,不同传输协议的Sender会提供具体的实现逻辑,其子类有:
ActiveMQSender、FakeSender、KafkaSender、LibthriftSender、OkHttpSender、RabbitMQSender、URLConnectionSender。

不同协议均按照自身协议规范执行发送逻辑,因为我们的Demo中用的是OkHttpSender,所以我们主要看看OkHttpSender是如何实现的。Demo中使用如下:

Sender sender = OkHttpSender.create(“http://localhost:9411/api/v2/spans”);
AsyncReporter asyncReporter = AsyncReporter.create(sender);

这里,通过AsyncReporter.create方法,我们将OkHttpSender注入到了Reporter中,那么接下来看看OkHttpSender的sendSpans方法实现:

@Override
public zipkin2.Call<Void> sendSpans(List<byte[]> encodedSpans) {
if (closeCalled) throw new IllegalStateException(“closed”);
Request request;
try {
request = newRequest(encoder.encode(encodedSpans));
} catch (IOException e) {
throw zipkin2.internal.Platform.get().uncheckedIOException(e);
}
return new HttpCall(client.newCall(request));
}

执行完这个方法后,会返回一个HttpCall,Reporter的flush方法 中会调用HttpCall的execute方法 ,完成Http请求发送。

Brave Tracer

Span数据结构,包括发送Span的组件我们搞清楚了,那么谁来负责创建Span了?这就是Tracer的工作,他负责创建Span及提供Span的各种操作,主要方法如下表所示:

Brave Tracing

现在Span有了,创建Span的组件有了,发送Span的组件也有了,那就只需要一个把他们组合起来的类似工厂的角色了,那就是Tracing,他的主要工作就是连接服务器,然后利用Tracer创建出Span,接着发送Span到zipkin服务端。

Tracing源码采用的Builder模式,再看看我们Demo中创建Tracing的代码:

Tracing tracing = Tracing.newBuilder()
.localServiceName(“my-service”)
.spanReporter(asyncReporter)
.build();

我们Tracing.newBuilder()创建了一个Tracing的Builder,然后指定了这个Tracing的服务名,使用什么Reporter,接着调用了Builder的build方法,我们看看build方法代码:

public Tracing build() {
if (clock null) clock = Platform.get().clock();
if (localIp null) localIp = Platform.get().linkLocalIp();
if (spanReporter == null) spanReporter = new LoggingReporter();
return new Default(this);
}

它调用了Tracing的默认实现,默认实现子类如下:

static final class Default extends Tracing {
final Tracer tracer;
final Propagation.Factory propagationFactory;
final Propagation<String> stringPropagation;
final CurrentTraceContext currentTraceContext;
final Sampler sampler;
final Clock clock;
final ErrorParser errorParser;
<span class="n">Default</span><span class="o">(</span><span class="n">Builder</span> <span class="n">builder</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">this</span><span class="o">.</span><span class="na">clock</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">clock</span><span class="o">;</span>
  <span class="k">this</span><span class="o">.</span><span class="na">errorParser</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">errorParser</span><span class="o">;</span>
  <span class="k">this</span><span class="o">.</span><span class="na">propagationFactory</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">propagationFactory</span><span class="o">;</span>
  <span class="k">this</span><span class="o">.</span><span class="na">stringPropagation</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">propagationFactory</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">Propagation</span><span class="o">.</span><span class="na">KeyFactory</span><span class="o">.</span><span class="na">STRING</span><span class="o">);</span>
  <span class="k">this</span><span class="o">.</span><span class="na">currentTraceContext</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">currentTraceContext</span><span class="o">;</span>
  <span class="k">this</span><span class="o">.</span><span class="na">sampler</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">sampler</span><span class="o">;</span>
  <span class="n">zipkin2</span><span class="o">.</span><span class="na">Endpoint</span> <span class="n">localEndpoint</span> <span class="o">=</span> <span class="n">zipkin2</span><span class="o">.</span><span class="na">Endpoint</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">()</span>
      <span class="o">.</span><span class="na">serviceName</span><span class="o">(</span><span class="n">builder</span><span class="o">.</span><span class="na">localServiceName</span><span class="o">)</span>
      <span class="o">.</span><span class="na">ip</span><span class="o">(</span><span class="n">builder</span><span class="o">.</span><span class="na">localIp</span><span class="o">)</span>
      <span class="o">.</span><span class="na">port</span><span class="o">(</span><span class="n">builder</span><span class="o">.</span><span class="na">localPort</span><span class="o">)</span>
      <span class="o">.</span><span class="na">build</span><span class="o">();</span>
  <span class="n">SpanReporter</span> <span class="n">reporter</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SpanReporter</span><span class="o">(</span><span class="n">localEndpoint</span><span class="o">,</span> <span class="n">builder</span><span class="o">.</span><span class="na">reporter</span><span class="o">,</span> <span class="n">noop</span><span class="o">);</span>
  <span class="k">this</span><span class="o">.</span><span class="na">tracer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Tracer</span><span class="o">(</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">clock</span><span class="o">,</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">propagationFactory</span><span class="o">,</span>
      <span class="n">reporter</span><span class="o">,</span>
      <span class="k">new</span> <span class="n">PendingSpans</span><span class="o">(</span><span class="n">localEndpoint</span><span class="o">,</span> <span class="n">clock</span><span class="o">,</span> <span class="n">reporter</span><span class="o">,</span> <span class="n">noop</span><span class="o">),</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">sampler</span><span class="o">,</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">errorParser</span><span class="o">,</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">currentTraceContext</span><span class="o">,</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">traceId128Bit</span> <span class="o">||</span> <span class="n">propagationFactory</span><span class="o">.</span><span class="na">requires128BitTraceId</span><span class="o">(),</span>
      <span class="n">builder</span><span class="o">.</span><span class="na">supportsJoin</span> <span class="o">&amp;&amp;</span> <span class="n">propagationFactory</span><span class="o">.</span><span class="na">supportsJoin</span><span class="o">(),</span>
      <span class="n">noop</span>
  <span class="o">);</span>
  <span class="n">maybeSetCurrent</span><span class="o">();</span>
<span class="o">}</span></code></pre></div><p data-pid="hLRJsNYx">从上面可以看到,主要干的工作有:</p><ul><li data-pid="D8MUw-l6">根据Spanreporter,生成FinishedSpanHandler,发送Span用;</li><li data-pid="e3wIFPcU">根据FinishedSpanHandler以及其他默认信息生成Tracer;</li></ul><p data-pid="Al96zlnF">OK,现在对于DEMO中的Brave的上报数据流程和原理是不是清楚了不少!</p><h2>链路上报总结</h2><p data-pid="jS6Z95-M">Zipkin链路上报看起来很复杂,其实剥离各种封装,去除各种组件,其主线逻辑就是如下三步:</p><ul><li data-pid="EOuCr9rz">构造span对象,包括traceId,parentId,以及其自身的spanId等参数;</li><li data-pid="RvUaMLj6">选一种编解码协议,比如JSON,或者THRIF,或者PROTO3对Span进行编码;</li><li data-pid="qjsV8ah7">将编码后的span,通过利用一种传输协议上报到服务端;</li></ul><p data-pid="aYf0Sa9q">还有一块未分析:Brave是如何Support各个组件的。因为本文内容较多,放到以后的文章分析!</p></span></div></div></span><div><div class="ContentItem-time"><a target="_blank" href="//www.zhihu.com/question/361486592/answer/2296653849"><span data-tooltip="发布于 2022-01-04 00:15" aria-label="发布于 2022-01-04 00:15">编辑于 2022-01-04 00:34</span></a></div></div><span></span><div><div class="ContentItem-actions Sticky RichContent-actions is-fixed is-bottom" style="width: 694px; bottom: 0px; left: 16px;"><span><button aria-label="赞同 15 " aria-live="polite" type="button" class="Button VoteButton VoteButton--up FEfUrdfMIKpQDJDqkjte"><span style="display: inline-flex; align-items: center;">​<svg width="10" height="10" viewBox="0 0 24 24" class="Zi Zi--TriangleUp VoteButton-TriangleUp" fill="currentColor"><path fill-rule="evenodd" d="M13.792 3.681c-.781-1.406-2.803-1.406-3.584 0l-7.79 14.023c-.76 1.367.228 3.046 1.791 3.046h15.582c1.563 0 2.55-1.68 1.791-3.046l-7.79-14.023Z" clip-rule="evenodd"></path></svg></span>赞同 15</button><button aria-label="反对" aria-live="polite" type="button" class="Button VoteButton VoteButton--down FEfUrdfMIKpQDJDqkjte"><span style="display: inline-flex; align-items: center;">​<svg width="10" height="10" viewBox="0 0 24 24" class="Zi Zi--TriangleDown" fill="currentColor"><path fill-rule="evenodd" d="M13.792 20.319c-.781 1.406-2.803 1.406-3.584 0L2.418 6.296c-.76-1.367.228-3.046 1.791-3.046h15.582c1.563 0 2.55 1.68 1.791 3.046l-7.79 14.023Z" clip-rule="evenodd"></path></svg></span></button></span><button type="button" class="Button ContentItem-action FEfUrdfMIKpQDJDqkjte Button--plain Button--withIcon Button--withLabel fEPKGkUK5jyc4fUuT0QP B46v1Ak6Gj5sL2JTS4PY RuuQ6TOh2cRzJr6WlyQp"><span style="display: inline-flex; align-items: center;">​<svg width="1.2em" height="1.2em" viewBox="0 0 24 24" class="Zi Zi--Comment Button-zi t2ntD6J1DemdOdvh5FB4" fill="currentColor"><path fill-rule="evenodd" d="M12 2.75a9.25 9.25 0 1 0 4.737 17.197l2.643.817a1 1 0 0 0 1.25-1.25l-.8-2.588A9.25 9.25 0 0 0 12 2.75Z" clip-rule="evenodd"></path></svg></span>2 条评论</button><div class="Popover ShareMenu ContentItem-action"><div class="ShareMenu-toggler" id="Popover27-toggle" aria-haspopup="true" aria-expanded="false" aria-owns="Popover27-content"><button type="button" class="Button FEfUrdfMIKpQDJDqkjte Button--plain Button--withIcon Button--withLabel fEPKGkUK5jyc4fUuT0QP B46v1Ak6Gj5sL2JTS4PY RuuQ6TOh2cRzJr6WlyQp"><span style="display: inline-flex; align-items: center;">​<svg width="1.2em" height="1.2em" viewBox="0 0 24 24" class="Zi Zi--Share Button-zi t2ntD6J1DemdOdvh5FB4" fill="currentColor"><path d="M19.47 1.914a.8.8 0 0 1 1.204.778l-1.872 16.386a.9.9 0 0 1-1.204.743l-4.615-1.692a.7.7 0 0 0-.831.28l-1.927 3.02c-.43.674-1.474.369-1.474-.43v-3.865a.8.8 0 0 1 .179-.504l5.808-7.148a.595.595 0 0 0-.897-.781l-5.93 6.354a1.1 1.1 0 0 1-1.258.252L2.57 13.46a.8.8 0 0 1-.08-1.415l16.98-10.13Z"></path></svg></span>分享</button></div></div><button type="button" class="Button ContentItem-action FEfUrdfMIKpQDJDqkjte Button--plain Button--withIcon Button--withLabel fEPKGkUK5jyc4fUuT0QP B46v1Ak6Gj5sL2JTS4PY RuuQ6TOh2cRzJr6WlyQp"><span style="display: inline-flex; align-items: center;">​<svg width="1.2em" height="1.2em" viewBox="0 0 24 24" class="Zi Zi--Star Button-zi t2ntD6J1DemdOdvh5FB4" fill="currentColor"><path d="M10.484 3.307c.673-1.168 2.358-1.168 3.032 0l2.377 4.122a.25.25 0 0 0 .165.12l4.655.987c1.319.28 1.84 1.882.937 2.884l-3.186 3.535a.25.25 0 0 0-.063.193l.5 4.733c.142 1.34-1.222 2.33-2.453 1.782l-4.346-1.938a.25.25 0 0 0-.204 0l-4.346 1.938c-1.231.549-2.595-.442-2.453-1.782l.5-4.733a.25.25 0 0 0-.064-.193L2.35 11.42c-.903-1.002-.382-2.604.937-2.884l4.655-.987a.25.25 0 0 0 .164-.12l2.378-4.122Z"></path></svg></span>收藏</button><button aria-live="polite" type="button" class="Button ContentItem-action FEfUrdfMIKpQDJDqkjte Button--plain Button--withIcon Button--withLabel fEPKGkUK5jyc4fUuT0QP B46v1Ak6Gj5sL2JTS4PY RuuQ6TOh2cRzJr6WlyQp"><span style="display: inline-flex; align-items: center;">​<svg width="1.2em" height="1.2em" viewBox="0 0 24 24" class="Zi Zi--Heart Button-zi t2ntD6J1DemdOdvh5FB4" fill="currentColor"><path fill-rule="evenodd" d="M12.004 4.934c1.015-.944 2.484-1.618 3.98-1.618 3.48 0 6.53 3.265 6.15 7.614-.11 1.254-.686 2.55-1.458 3.753-.778 1.215-1.79 2.392-2.845 3.419-1.054 1.028-2.168 1.923-3.161 2.566a9.96 9.96 0 0 1-1.41.777c-.418.182-.862.32-1.268.32s-.848-.137-1.267-.317a9.918 9.918 0 0 1-1.407-.771c-.992-.64-2.103-1.53-3.156-2.555-1.052-1.024-2.062-2.2-2.84-3.417-.77-1.208-1.346-2.51-1.456-3.775-.38-4.349 2.67-7.614 6.15-7.614 1.484 0 2.983.673 3.988 1.618Z" clip-rule="evenodd"></path></svg></span>喜欢</button><div class="Popover ContentItem-action"><button aria-label="更多" id="Popover28-toggle" aria-haspopup="true" aria-expanded="false" aria-owns="Popover28-content" type="button" class="Button OptionsButton FEfUrdfMIKpQDJDqkjte Button--plain Button--withIcon Button--iconOnly fEPKGkUK5jyc4fUuT0QP B46v1Ak6Gj5sL2JTS4PY hIwDV_tcL6XN1HprrnAq"><span style="display: inline-flex; align-items: center;">​<svg width="1.2em" height="1.2em" viewBox="0 0 24 24" class="Zi Zi--Dots Button-zi t2ntD6J1DemdOdvh5FB4" fill="currentColor"><path d="M6 10.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM10.5 12a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM16.5 12a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z"></path></svg></span></button></div><button data-zop-retract-question="true" type="button" class="Button ContentItem-action ContentItem-rightButton FEfUrdfMIKpQDJDqkjte Button--plain fEPKGkUK5jyc4fUuT0QP"><span class="RichContent-collapsedText">收起</span><span style="display: inline-flex; align-items: center;">​<svg width="24" height="24" viewBox="0 0 24 24" class="Zi Zi--ArrowDown ContentItem-arrowIcon is-active" fill="currentColor"><path fill-rule="evenodd" d="M17.776 10.517a.875.875 0 0 1-.248 1.212l-5.05 3.335a.875.875 0 0 1-.964 0L6.47 11.73a.875.875 0 1 1 .965-1.46l4.56 3.015 4.568-3.016a.875.875 0 0 1 1.212.248Z" clip-rule="evenodd"></path></svg></span></button></div><div class="Sticky--holder" style="position: static; inset: auto; display: flex; float: none; margin: 0px -20px -10px; height: 54px; width: 694px;"></div></div></div>
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rainbowcheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值