分布式链路追踪是当今微服务架构中必不可少的组成部分,一个请求穿插于各个应用系统、中间件、数据库之间,通过 tracing 就能知道整个请求的链路以及各部分的耗时。Egg 会基于开放的 opentracing 来实现这个功能。
概念
Trace:一个 Trace 代表在微服务架构中的一次链路请求,可能经过一个或多个微服务应用。
Span:一个 span 代表系统中具有开始时间和执行时长的逻辑运行单元,一个 Trace 会包含多个 Span。
SpanContext:代表一个 Span 的信息,比如 TraceId,spanId,以及该 Span 的相关信息。注意:这个和 egg 的 ctx 没有关系。
Baggage:还有一些信息也在 SpanContext 中,我们称之为 Baggage,这些信息会在整个 Trace 中传递。
Tag:Span 中用来识别 span 的一些信息,比如
- error
- component: httplib, jdbc, mongoose
- span.kind: client, server
- http.url
- http.method
- http.status_code
- framework
- peer.hostname
Carrier:在微服务架构中,两个应用之前会通过不同的协议进行通讯,但是 opentracing 是无法感知的,所以定义了 Carrier 这个概念,他通过 Inject 和 Extract 和 SpanContext 之前进行互相转换,然后 Carrier 可以在协议中传递。比如 HTTP 请求,SpanContext 通过 Inject 设置 HTTPCarrier(转换成请求头),在请求接收方通过 Extract 将 HTTPCarrier 提取成 SpanContext。
Collector:我们通过 Collector 收集每个 span 的具体信息,然后记录,可以通过日志的方式或请求上报的方式。
实现方案
Egg 提供 ctx.tracer
实例作为一次 Trace,下面按照一个微服务应用整个请求周期描述各个 API
接收请求
比如收到了一个 HTTP 请求,在 server 入口应该
// app/middleware/meta.js
module.exports = () => {
return async function meta(ctx, next) {
// 从 http header 获取 span 上下文
const spanContext = ctx.tracer.extract('HTTP', ctx.header);
// 创建 span 时指定父级 span
const span = ctx.tracer.startSpan('http_server', { childOf: spanContext });
await next();
span.finish();
};
}
发送请求
比如发起一个 RPC 请求,需要在 RPC 客户端
const span = ctx.tracer.startSpan('rpcclient');
const rpcCarrier = {};
ctx.tracer.inject(span.context, 'RPC', rpcCarrier);
ctx.rpcclient.invoke(data, rpcCarrier);
span.finish();
定义 Carrier
上面使用到了 inject 和 extract 其实是需要这里提前定义的
// lib/http_carrier.js
module.exports = class HttpCarrier {
inject(spanContext) {
// 转换
return header;
}
extract(header) {
// 转换
return spanContext;
}
}
// config/config.default.js
exports.opentracing = {
carrier: {
'HTTP': require('../lib/http_carrier'),
},
};
定义 Collector
还需要定义一个收集器
// lib/log_collector.js
module.exports = class LogColletor {
collect(spanContext) {
this.ctx.logger.write(this.format(spanContext));
}
}
// config/config.default.js
exports.opentracing = {
collector: {
log: require('../lib/log_collector'),
},
};
覆盖默认实现
Egg 的 Tracer 和 Span 对象都是 opentracing 的一个实现,但你也可以通过覆盖的方式来实现。
// config/config.default.js
exports.opentracing = {
globalTracer: YourTracer,
};
分布式跟踪系统
Zipkin, Dapper, HTrace, X-Trace