世上本没有路,走的人多了,便变成了路 -- 鲁迅
本次讨论的话题就是需要在各个服务之间踏出条"路",让 bug 有"路"可循。
至于为什么用 jaeger... 这个支持多语言方案算么?遵循 opentracing 规范算么?开箱即用算么?还有更多?
至于为什么遵循 opentracing 规范的好... 这个...杠精同学,文末地址可参考
反正先撸袖开干...
了解 Jaeger
使用之前先了解:
Jaeger: open source, end-to-end distributed tracing
Jaeger: 开源的、分布式系统的端到端追踪
Monitor and troubleshoot transactions in complex distributed systems
在复杂的分布式系统之间做监控及问题排查的事务处理。
jaeger 体系和流程如下图
jaeger-client (OpenTracing API 各语言的实现,用于在应用中塞入信息采集点)
jaeger-agent (负责发送的进程,对 spans 进行处理并发送给 collector,监听 spans 的 UDP 发送。设计这层是为了作为基础组件部署到主机上,从 client 中抽象出了 collector 的发现和路由。注意:1.这层应该是部署在应用本地;2.如果配置报告的 endpoint,则直接将 spans 发送到 collector,不需要 agent。)
jaeger-collector (收集追踪 spans,并通过管道对追踪数据进行处理。当前的管道支持追踪的验证、索引、转换,最后存储数据)
data store (追踪信息的存储)
jaeger-query (从存储中检索追踪信息并通过 UI 展示)
jaeger-ui (UI 展示层,基于 React)
注意:jaeger 的存储是可插拔组件,目前支持 Cassandra、ElasticSearch 和 Kafka。
基于以上的体系结构,本文关注点在 jaeger-client 部分,怎么实现服务之间和服务内部的 tracing。
了解追踪信息
Span:追踪中的逻辑单元,比如一次请求的过程/一个函数的执行,包含操作名称、开始时间、持续时间。
SpanContext:表示需要传播到下游 Spans 和跨应用/进程的 Span 数据,可以简单理解为串在各个系统里的统一标识对象。
Baggage:字符串组成的键值对,和 Span/SpanContext 互相关联,会在所有的下游 Spans 中进行传播。(可以做一些强大的功能,如在整个链路夹带数据,使用成本高,小心使用)
Tracer:项目中的追踪实例,追踪项目里数据变化/函数执行的过程,可以认为是一个定向非循环的 spans 的集合图。
Tracer 和 Span 如下图:
对于 jaeger-ui 效果如下图:
jaeger-client 是 opentracing 的实现,于是 jaeger-client api 几乎等同于 opentracing api。
Api 和配置参考
本文以 Nodejs 为主,Go 为辅(因为当前刚好涉及到这两种服务的链路追踪方案 )。
这里大致介绍一下 Configuration/Tracer/Span ,以便实现一个基础的 tracing 。
Configuration
{
serviceName: "string",
disable: "boolean",
sampler: {
type: "string", // required
param: "number", // required
hostPort: "string",
host: "string",
port: "number",
refreshIntervalMs: "number"
},
reporter: {
logSpans: "boolean",
agentHost: "string",
agentPort: "number",
collectorEndpoint: "string",
username: "string",
password: "string",
flushIntervalMs: "number"
},
throttler: {
host: "string",
port: "number",
refreshIntervalMs: "number"
}
}
Tracer
{
objects: {
_tags: "object", // tags 信息,含 jaeger-version/hostname/ip/client-uuid
_metrics: "object", // Metrics 度量实例
_serviceName: "string", // 服务名称
_reporter: "object", // 提交实例
_sampler: "object", // 采样器实例
_logger: "object", // 日志实例,默认 NullLogger
_baggageSetter: "object", // BaggageSetter 实例
_debugThrottler: "object", // DefaultThrottler 配置实例
_injectors: "object", // 注入器列表
_extractors: "object", // 提取器列表
_process: "object" // process 信息,含 serviceName/tags
},
// 文件位置 ./jaeger-client-node/blob/master/src/tracer.js
methods: {
_startInternalSpan: "void", // 创建基础 span ,供 startSpan 方法调用 / params: spanContext(SpanContext) operationName(string) startTime(number) userTags(any) internalTags(any) parentContext?(SpanContext) rpcServer(boolean) references(Array) / retuen Span
_report: "void", // 发起数据提交,提交到jaeger后端 / params: span(Span)
registerInjector: "void", // 向 tracer 注入 "注入 SpanContext 内容的方式" / params: format(string) injector(Injector)
registerExtractor: "void", // 向 tracer 注入 "提取 SpanContext 内容的方式" / params: format(string) extractor(Extractor)
startSpan: "void", // 创建一个 Span / params: operationName(string) options?:{ operationName(string) childOf(SpanContext) references(Array) tags(object) startTime(number) }
inject: "void", // 将 SpanContext 注入到序列化格式的 carrier 中 / params: SpanConte