Brave核心lib
Brave是一个lib用于捕获,以及上报关于分布式操作的性能的信息到Zipkin。大多数用户不会直接时候Brave,而是把应用Brave到对应的框架上;
这个模块包含tracer(可以用来创建,并加入span),当然也包含一个lib去在不同的网络边界传播trace context(上下文),比如说http headers。
Setup
第一步,我们要配置一下,如何发送到Zipkin上。
如下是一个如何通过HTTP发送trace数据到Zipkin的一个样例:
// Configure a reporter, which controls how often spans are sent
// (this dependency is io.zipkin.reporter2:zipkin-sender-okhttp3)
sender = OkHttpSender.create("http://127.0.0.1:9411/api/v2/spans");
// (this dependency is io.zipkin.reporter2:zipkin-reporter-brave)
zipkinSpanHandler = AsyncZipkinSpanHandler.create(sender);
// Create a tracing component with the service name you want to see in Zipkin.
// 可以想见,一个tracing对应一个服务
tracing = Tracing.newBuilder()
.localServiceName("my-service")
.addSpanHandler(zipkinSpanHandler)
.build();
// Tracing exposes objects you might need, most importantly the tracer
// Tracing 暴露提供你需要的对象,最终要的是Tracer,tracer存在ThreadLocal中?
tracer = tracing.tracer();
// Failing to close resources can result in dropped spans! When tracing is no
// longer needed, close the components you made in reverse order. This might be
// a shutdown hook for some users.
tracing.close();
// tracing 应该是一个链路结束是关闭,ZipkinSpanHandler 和 sender 应该是系统结束前关闭!
zipkinSpanHandler.close();
sender.close();
Tracing
tracer创建并加入span。tracer可以通过设置采样率来控制发送到Zipkin的数据量。
tracer完成发送数据到Zipkin后,会返回Span。当开启一个span,你可以这个span添加关心的event和tag;
span包含context,可以用来标识它在分布式操作中的位置
// Start a new trace or a span within an existing trace representing an operation
ScopedSpan span = tracer.startScopedSpan("encode");
try {
// The span is in "scope" meaning downstream code such as loggers can see trace IDs
return encoder.encode();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // always finish the span
}
// Start a new trace or a span within an existing trace representing an operation
Span span = tracer.nextSpan().name("encode").start();
// Put the span in "scope" so that downstream code such as loggers can see trace IDs
try (SpanInScope ws = tracer.withSpanInScope(span)) {
return encoder.encode();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // note the scope is independent of the span. Always finish a span.
}
如上两个示例,作用是完全相同的,生成的span要么是一个全新的root span,要么是一个已存在的trace的子span;
定制span
一旦你获取到了span,你可以给span添加tag,tag可以很好的提现span的特性。比如说你可以给你的span添加你的运行时版本;
span.tag("clnt/finagle.version", "6.36.0");
当你蛮对各种各样的Span时,Tag类会对你有帮助。对于通用的或者重量级的tag,尝试使用Tag来定义并使用;
SUMMARY_TAG = new Tag<Summarizer>("summary") {
@Override protected String parseValue(Summarizer input, TraceContext context) {
return input.computeSummary();
}
}
// This works for any variant of span
SUMMARY_TAG.tag(summarizer, span);
当期望暴露自定义span的能力给第三方时,brave.SpanCustomizer会是比brave.Span更好的选择。前者更易于理解和测试,并且不会诱导用户去操作span的lifecycle hooks。
interface MyTraceCallback {
void request(Request request, SpanCustomizer customizer);
}
for (MyTraceCallback callback : userCallbacks) {
callback.request(request, span);
}
// Some DI configuration wires up the current span customizer
@Bean SpanCustomizer currentSpanCustomizer(Tracing tracing) {
return CurrentSpanCustomizer.create(tracing);
}
// user code can then inject this without a chance of it being null.
@Inject SpanCustomizer span;
void userCode() {
span.annotate("tx.started");
...
}
#### Remote spans Remote spans是指,同一个trace涉及到不同进程(服务),需要在进程间传递上下文信息;作为调用方,你通常需要: 1. 启用一个span,并加trace信息添加到request中 2. 把新的span放进到新的scope中 3. 指定调用 4. 捕获任何异常 5. 完结span
requestWrapper = new ClientRequestWrapper(request);
span = tracer.nextSpan(sampler, requestWrapper); // 1.
tracing.propagation().injector(ClientRequestWrapper::addHeader)
.inject(span.context(), requestWrapper);
span.kind(request.spanKind());
span.name("Report");
span.start();
try (Scope ws = currentTraceContext.newScope(span.context())) { // 2.
return invoke(request); // 3.
} catch (Throwable e) {
span.error(error); // 4.
throw e;
} finally {
span.finish(); // 5.
}
Sampling
Sampling可以减少被采样的span数,不采样的span标记为no overhead(noop);
是否采样是一个up-front决定,意味着,是否采样是在trace第一次操作的时候就被决定的,并且这个决定会一直向下传播到整个trace;也就是一个trace要么整个被采样,要么整个不被采样;
你可以象如下一样自定义自己的sample策略:
tracing = Tracing.newBuilder()
.sampler(RateLimitingSampler.create(10))
--snip--
.build();
--------------------------------------------------------------------
SamplerFunction<Request> requestBased = (request) -> {
if (request.url().startsWith("/experimental")) {
return true;
} else if (request.url().startsWith("/static")) {
return false;
}
return null;
};
Span nextSpan(final Request input) {
return tracer.nextSpan(requestBased, input);
}
有些人可能希望基于java方法上的注释来决定是否采样。
// derives a sample rate from an annotation on a java method
SamplerFunction<Traced> samplerFunction = DeclarativeSampler.createWithRate(Traced::sampleRate);
@Around("@annotation(traced)")
public Object traceThing(ProceedingJoinPoint pjp, Traced traced) throws Throwable {
// When there is no trace in progress, this overrides the decision based on the annotation
ScopedSpan span = tracer.startScopedSpan(spanName(pjp), samplerFunction, traced);
try {
return pjp.proceed();
} catch (RuntimeException | Error e) {
span.error(e);
throw e;
} finally {
span.finish();
}
}
Baggage
有时候,你需要传递额外的字段;比如说,你需要知道一个具体请求的countryCode,你能通过HTTP头来传递;
import brave.baggage.BaggagePropagationConfig.SingleBaggageField;
// Configure your baggage field
COUNTRY_CODE = BaggageField.create("country-code");
// When you initialize the builder, add the baggage you want to propagate
tracingBuilder.propagationFactory(
BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY)
.add(SingleBaggageField.remote(COUNTRY_CODE))
.build()
);
// Later, you can retrieve that country code in any of the services handling the trace
// and add it as a span tag or do any other processing you want with it.
countryCode = COUNTRY_CODE.getValue(context);
只要字段是通过BaggagePropagation配置的,就可以本地区读取或者更新;
COUNTRY_CODE.updateValue("FO");
String countryCode = COUNTRY_CODE.getValue();
//or
COUNTRY_CODE.updateValue(span.context(), "FO");
String countryCode = COUNTRY_CODE.get(span.context());
Tags.BAGGAGE_FIELD.tag(COUNTRY_CODE, span);