上一节,我们讲解了,brave-web-filter的运行原理,其实别的都不用看,直接拿到最后一张图就可以构造自己的brave-web-filter了。你懂的。
---这一节,我们需要完成一个任务,就是如果在tomcat里,发出了一个新的HTTP请求到后端,如何记住这个操作的信息,耗时等,并上报到zipkin server呢?
参考 :http://www.cnblogs.com/java-zhao/p/5838819.html
接下来就来解决这个问题。
---首先是技术选型,这里有很多client,我选择了Brave-OkHttp.
下载版本 3.16.0的brave_okhttp
http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.zipkin.brave%22%20AND%20a%3A%22brave-okhttp%22
然后测试代码如下:
@SuppressWarnings("deprecation")
private void test() {
// 构造spanCollector
SpanCollector spanCollector = new LoggingSpanCollector();
// HttpSpanCollector.Config spanConfig =
// HttpSpanCollector.Config.builder().compressionEnabled(false)//
// 默认false,span在transport之前是否会被gzipped。
// .connectTimeout(5000)// 5s,默认10s
// .flushInterval(1)// 1s
// .readTimeout(6000)// 5s,默认60s
// .build();
// spanCollector= HttpSpanCollector.create("http://1.2.3.4:9411",
// spanConfig, new EmptySpanCollectorMetricsHandler());
// 构造Brave
Brave.Builder builder = new Brave.Builder("service1");// 指定serviceName
builder.spanCollector(spanCollector);
builder.traceSampler(Sampler.create(1));// 采集率
Brave brave = builder.build();
// 假设这里有Http交互
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new BraveOkHttpRequestResponseInterceptor(brave.clientRequestInterceptor(),
brave.clientResponseInterceptor(), new DefaultSpanNameProvider()))
.build();
Request request = new Request.Builder().url("http://www.163.com").build();
Response response;
try {
response = client.newCall(request).execute();
response.body().string();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
---接下来就开始debug了。
Step completed: "thread=http-nio-8080-exec-1", okhttp3.internal.http.RealInterceptorChain.proceed(), line=91 bci=183
91 Interceptor interceptor = interceptors.get(index);
http-nio-8080-exec-1[1] next
>
Step completed: "thread=http-nio-8080-exec-1", okhttp3.internal.http.RealInterceptorChain.proceed(), line=92 bci=201
92 Response response = interceptor.intercept(next);
http-nio-8080-exec-1[1] print interceptor
interceptor = "com.github.kristofa.brave.okhttp.BraveOkHttpRequestResponseInterceptor@f9725d"
http-nio-8080-exec-1[1] step
>
Step completed: "thread=http-nio-8080-exec-1", com.github.kristofa.brave.okhttp.BraveOkHttpRequestResponseInterceptor.intercept(), line=69 bci=0
69 Request request = chain.request();
http-nio-8080-exec-1[1]
所以,你只要在下面的断点处开始跟踪就好了
stop in com.github.kristofa.brave.okhttp.BraveOkHttpRequestResponseInterceptor.intercept
接下来会执行
requestInterceptor.handle(new HttpClientRequestAdapter(okHttpRequest, spanNameProvider));
表示开始记录执行之前的信息点了,看看做了哪些事情。
Step completed: "thread=http-nio-8080-exec-1", com.github.kristofa.brave.ClientTracer.getNewSpanId(), line=149 bci=0
149 Span parentSpan = spanAndEndpoint().state().getCurrentLocalSpan();
http-nio-8080-exec-1[1] next
>
Step completed: "thread=http-nio-8080-exec-1", com.github.kristofa.brave.ClientTracer.getNewSpanId(), line=150 bci=13
150 if (parentSpan == null) {
http-nio-8080-exec-1[1] print parentSpan
parentSpan = null
http-nio-8080-exec-1[1] next
>
Step completed: "thread=http-nio-8080-exec-1", com.github.kristofa.brave.ClientTracer.getNewSpanId(), line=151 bci=17
151 ServerSpan serverSpan = spanAndEndpoint().state().getCurrentServerSpan();
http-nio-8080-exec-1[1] next
>
Step completed: "thread=http-nio-8080-exec-1", com.github.kristofa.brave.ClientTracer.getNewSpanId(), line=152 bci=30
152 if (serverSpan != null) {
http-nio-8080-exec-1[1] print serverSpan
serverSpan = "ServerSpan{span={"traceId":"88a037934ebddc71","id":"88a037934ebddc71","name":"get","timestamp":1484231320269000,"annotations":[{"timestamp":1484231320269000,"value":"sr","endpoint":{"serviceName":"myservicename","ipv4":"192.168.56.102"}}],"binaryAnnotations":[{"key":"http.url","value":"/myeyeweb/","endpoint":{"serviceName":"myservicename","ipv4":"192.168.56.102"}}]}, sample=true}"
首先取出parentSpan,如果不存在localSpan,就从serverSpan里取,这样才可以复用全局traceId.
PS:
一定要时刻记住这里有3个ThreadLocal变量
private final static ThreadLocal<ServerSpan> currentServerSpan = new ThreadLocal<ServerSpan>() {
@Override
protected ServerSpan initialValue() {
return ServerSpan.EMPTY;
}
};
private final static ThreadLocal<Span> currentClientSpan = new ThreadLocal<Span>();
private final static ThreadLocal<Span> currentLocalSpan = new ThreadLocal<Span>();
接着上面来说,取出parentSpan后,构造好本次的client span,然后放到client ThreadLocal里去,然后就开始需要把相关的参数反映到requet里进行传递
@Override
public void addSpanIdToRequest(@Nullable SpanId spanId) {
if (spanId == null) {
request.addHeader(BraveHttpHeaders.Sampled.getName(), "0");
} else {
request.addHeader(BraveHttpHeaders.Sampled.getName(), "1");
request.addHeader(BraveHttpHeaders.TraceId.getName(), spanId.traceIdString());
request.addHeader(BraveHttpHeaders.SpanId.getName(), IdConversion.convertToString(spanId.spanId));
if (spanId.nullableParentId() != null) {
request.addHeader(BraveHttpHeaders.ParentSpanId.getName(), IdConversion.convertToString(spanId.parentId));
}
}
}
这样,下一个接收处理的才可以拿到全局的一些信息。
处理完request后,需要执行
responseInterceptor.handle(new HttpClientResponseAdapter(new OkHttpResponse(response)));
最后,把当前 span 转化成字符串
/** Changes this to a zipkin-native span object. */
public zipkin.Span toZipkin() {
zipkin.Span.Builder result = zipkin.Span.builder();
result.traceId(getTrace_id());
result.traceIdHigh(getTrace_id_high());
result.id(getId());
result.parentId(getParent_id());
result.name(getName());
result.timestamp(getTimestamp());
result.duration(getDuration());
result.debug(isDebug());
for (Annotation a : getAnnotations()) {
result.addAnnotation(zipkin.Annotation.create(a.timestamp, a.value, from(a.host)));
}
for (BinaryAnnotation a : getBinary_annotations()) {
result.addBinaryAnnotation(zipkin.BinaryAnnotation.builder()
.key(a.key)
.value(a.value)
.type(zipkin.BinaryAnnotation.Type.fromValue(a.type.getValue()))
.endpoint(from(a.host))
.build());
}
return result.build();
}
下面结合一个场景,讲述完整的信息
浏览器--->Tomcat Server (内部触发http请求到163主页),记录下完整的信息如下:
INFO:
{
"traceId":"46b7687dbc61fb03",
"id":"8f2cdd70e5ecd299",
"name":"get",
"parentId":"46b7687dbc61fb03",
"timestamp":1484238440149000,
"duration":199688,
"annotations":
[
{"timestamp":1484238440149000,"value":"cs","endpoint":{"serviceName":"service1","ipv4":"192.168.56.102"}},
{"timestamp":1484238440348688,"value":"cr","endpoint":{"serviceName":"service1","ipv4":"192.168.56.102"}}],
"binaryAnnotations":
[
{"key":"http.url","value":"http://www.163.com/","endpoint":{"serviceName":"service1","ipv4":"192.168.56.102"}}
]
}
Jan 13, 2017 12:27:21 AM com.github.kristofa.brave.LoggingReporter report
INFO:
{
"traceId":"46b7687dbc61fb03",
"id":"46b7687dbc61fb03",
"name":"get",
"timestamp":1484238440117000,
"duration":948634,
"annotations":
[
{
timestamp":1484238440117000,
"value":"sr",
"endpoint":{"serviceName":"myservicename","ipv4":"192.168.56.102"}
},
{
"timestamp":1484238441065634,
"value":"ss",
"endpoint":{"serviceName":"myservicename","ipv4":"192.168.56.102"}
}
],
"binaryAnnotations":
[
{"key":"http.status_code","value":"200","endpoint":{"serviceName":"myservicename","ipv4":"192.168.56.102"}},
{"key":"http.url","value":"/myeyeweb/","endpoint":{"serviceName":"myservicename","ipv4":"192.168.56.102"}}
]
}
Jan 13, 2017 12:27:21 AM com.github.kristofa.brave.LoggingReporter report
可以看到,这2个有一个全局统一的global ID.