当前位置:网站首页>【分布式】链路追踪 jaeger
【分布式】链路追踪 jaeger
2022-08-08 05:53:00 【shanxiaoshuai】
最近做了一点traces相关的工作,看了关于jaeger的一些内容,来水一篇。(艰难地保持着一月一篇)
为什么需要链路追踪
随着应用的发展,分布式是不可避免的趋势,无论是随着业务的复杂庞大由单体应用拆分为微服务、出于扩展以及容灾的考虑将服务多机房部署多份还是各种分布式中间件的引入等原因。分布式使应用各方面的能力大幅提升,但同时使应用的复杂度大幅提高,问题定位变得困难。
链路追踪traces,顾名思义,就是记录一个请求的调用链路。其侧重点为调用的链路,通常是以有向无环图的形式展示,能反应服务之间的依赖关系。在系统的可观测性领域,与traces并列的是mertics和logs。metrics侧重于指标,包括通用的系统性能指标,例如cpu、内存等,以及开发者关心的业务指标,例如时延、用户数等,通过指标可以展示系统的状态。logs的数据最为零散但是最为详细,其不够系统但能提供最具体最深入的信息。
在分布式环境中,一个请求会有很长的、横跨多个服务或者中间件的、复杂的调用链路。当想分析某个请求为什么出错时,traces能帮助我们迅速定位出错的环节,这就是为什么需要分布式的链路追踪。
分布式链路追踪业界有很多实现,但是原理上差不多。因为我使用的是jaeger,所以会以jaeger为例。另外不会讲太多具体实现的内容,例如traces数据的序列化、传输、存储、检索等等,主要的内容会放在基本概念、数据结构、如何使用。如果后面有时间,会再写一些jaeger的演进的内容,都是来自这篇Evolving Distributed Tracing at Uber Engineering,可能会对我们迭代产品有一些启发。
基本概念
jaeger中的数据模型如下。下面会结合数据模型一起介绍基本的概念。
trace和span
trace和span是分布式链路追踪最基本的概念。trace代表一个请求的链路,会有一个全局唯一的trace_id标识;span代表请求中的一个具体的执行单元,在同一个trace下每个span都有一个唯一的span_id进行标识。
在数据模型中可以看到trace并没有具体的数据结构,其是由同属于一个trace_id的span组织而成。span是主要的数据结构,其携带了详细的调用信息,主要包括这些字段:opreationName表示span的主要操作,是由开发人员进行赋值的;startTime和duration携带了时间相关的信息;tags和logs都是kv形式的数据,只不过logs携带了时间戳。
span中除了携带调用相关的信息外还携带了span之间的关联数据。看references字段知道span之间有父子关系(child_of)和顺序执行(follow_from)的关系,span按照相互之间关系组织起来就是trace。trace通常表现为由span组成的有向无环图。下图展示了trace和span之间的关系。
jaeger client使用
demo如下。
首先初始化全局的tracer对象。
func JaegerInit() (opentracing.Tracer, io.Closer) {
cfg := &config.Configuration{
ServiceName: "MY_PROJECT_NAME",
Sampler: &config.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "127.0.0.1:6831",
},
Tags: []opentracing.Tag{
},
}
tracer, closer, err := cfg.NewTracer()
if err != nil {
panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
}
// 这里顺便将 tracer 放入全局范围,opentracing 其他 api 的内部
// 实现会使用该全局的 tracer
opentracing.SetGlobalTracer(tracer)
return tracer, closer
}
开始记录trace数据。
func main() {
// "main" 是上面提到的operationName。通常是根据业务自定义名称,
// 这里 StartSpan 没有传递任何的 opentracing.StartSpanOption 参数,
// 所以得到的 span 是 root span。
span := tracer.StartSpan("start")
defer span.Finish()
// 将 span 存放到 context 中,其他函数可以从这个 context 中提取出
// span 的拓扑关系
ctx := opentracing.ContextWithSpan(context.Background(), span)
helloStr := methodA(ctx, helloTo)
methodB(ctx, helloStr)
}
func methodA(ctx context.Context, helloTo string) string {
// 从 context 中提取出span,如果 context 中没有span,
// 则这里得到的 span 将是 root span。所以如果 span 在链路的前进过程中忘记
// 传递,将会导致断链。
span, _ := opentracing.StartSpanFromContext(ctx, "methodA")
defer span.Finish()
return "Hello! " + helloTo
}
func methodB(ctx context.Context, helloStr string) {
span, _ := opentracing.StartSpanFromContext(ctx, "methodB")
defer span.Finish()
}
上面演示的是大多数情况下的场景,如果是调用的第一环就创建rootspan,否则可以从ctx中拿到相关信息并创建child span。ctx中传递数据其实本质是通过携带spanContext的数据结构实现的。spanContext的结构如下。
// SpanContext represents propagated span identity and state
type SpanContext struct {
// traceID represents globally unique ID of the trace.
// Usually generated as a random number.
traceID TraceID
// spanID represents span ID that must be unique within its trace,
// but does not have to be globally unique.
spanID SpanID
// parentID refers to the ID of the parent span.
// Should be 0 if the current span is a root span.
parentID SpanID
// Distributed Context baggage. The is a snapshot in time.
baggage map[string]string
// debugID can be set to some correlation ID when the context is being
// extracted from a TextMap carrier.
//
// See JaegerDebugHeader in constants.go
debugID string
// samplingState is shared across all spans
samplingState *samplingState
// remote indicates that span context represents a remote parent
remote bool
}
但是很多情况下调用链路中不一定全是rpc请求,比如中间可能会经历http调用或者kafka等消息队列。这些情况在jaeger中是通过"uber-trace-id"的key来传递信息的,其value为trace-id:span-id:parent-span-id:sample的字符串,该信息会设置在http的header或者kafka的message中。这种情况下的demo如下。
func main() {
clientContext, _ := opentracing.GlobalTracer().Extract(opentracing.TextMap, opentracing.TextMapCarrier(map[string]string{
"uber-trace-id": traceId}))
span := tracer.StartSpan("start",opentracing.ChildOf(clientContext))
defer span.Finish()
ctx := opentracing.ContextWithSpan(context.Background(), span)
helloStr := methodA(ctx, helloTo)
methodB(ctx, helloStr)
}
边栏推荐
- Preprocessing Notes
- Typescript namespace
- KDD'22 Recommendation System Papers (24 Research & 36 Application Papers)
- 数据库系统原理与应用教程(080)—— MySQL 练习题:操作题 186-193(二十四):综合练习
- 卷积神经网络 图像识别,卷积神经网络 图像处理
- 【Win10】Several sleep problems and countermeasures
- 如何批量导入文件,并全部自定义重命名为相同文件名
- webstorage
- Matlab simulation of photovoltaic mppt maximum power control based on disturbance observation method
- 预处理笔记
猜你喜欢
随机推荐
为什么互联网大厂一边疯狂裁员,一边不停招聘?
IP核之RAM实验
【猜拳游戏 基于Objective-C语言】
Redis 的内存策略
Tensorboard的使用 ---- SummaryWriter类(pytorch版)
【MySQL】——事务的基本概念
webstorage
MySQL5
MySQL6
【matlab】matlab中变量赋值函数deal
查询时间内用户分布的sql语句
状态压缩复习
76. The minimum cover substring
[Redis] Redis Learning - Transaction
Matlab simulation of photovoltaic mppt maximum power control based on disturbance observation method
值得收藏的几个postman特色功能帮你事半功倍!
七千字带你了解封装等机制
webstorage
Distributed Transactions: A Reliable Message Eventual Consistency Scheme
0字典树/字符串中等 LeetCode676. 实现一个魔法字典

![[u-boot] Analysis of the driver model of u-boot](/img/c2/d8ac24a3cfafc4d9bcbd1ed5c5f89b.png)







