链路追踪
分布式追踪论文
论文地址:http://bigbully.github.io/Dapper-translation/ 11
Opentracing协议
协议标准
中文文档:
https://zhuanlan.zhihu.com/p/...
https://github.com/opentracin...
OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。
Span状态:
- SpanContext,Span上下文对象
Span间状态:
ChildOf
(父子) :一个span可能是一个父级span的孩子,即"ChildOf"关系。FollowsFrom
(跟随):一些父级节点不以任何方式依赖他们子节点的执行结果
API
Tracer
接口用来创建Span
,以及处理如何处理Inject
(serialize) 和Extract
(deserialize),用于跨进程边界传递。- 当
Span
结束后(span.finish()
),除了通过Span
获取SpanContext
外,下列其他所有方法都不允许被调用。
协议作用
- OpenTracing通过提供平台无关、厂商无关的API,使得开发人员能够方便的添加(或更换)追踪系统的实现。
- 可以很自由的在不同的分布式追踪系统中切换
- 不负责具体实现
主要组成
- Trace:一个Trace代表一个事务或者流程在(分布式)系统中的执行过程
- Span:记录Trace在执行过程中的信息
- 无限极分类:服务与服务之间使用无限极分类的方式,通过HTTP头部或者请求地址传输到最低层,从而把整个调用链串起来。
数据模型
OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。
Span状态:
- SpanContext,Span上下文对象
Span间状态:
ChildOf
(父子) :一个span可能是一个父级span的孩子,即"ChildOf"关系。FollowsFrom
(跟随):一些父级节点不以任何方式依赖他们子节点的执行结果
API
Tracer
接口用来创建Span
,以及处理如何处理Inject
(serialize) 和Extract
(deserialize),用于跨进程边界传递。- 当
Span
结束后(span.finish()
),除了通过Span
获取SpanContext
外,下列其他所有方法都不允许被调用。
常见链路追踪软件
Jaeger,Zipkin,Skywalking
Jaeger
官网:https://www.jaegertracing.io/
结构
Agent,Collector,Query,Ingester
安装
docker-compose.yaml
version: "3"
services:
jaeger:
image: jaegertracing/all-in-one:1.32
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "14250:14250"
- "14268:14268"
- "14269:14269"
- "9411:9411"
运行
docker-compose up -d
访问
Go集成Jaeger
基础使用
micro/cartorder/jaeger/main.go
package main
import (
"time"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
)
func main() {
cfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.31.51:6831",
},
ServiceName: "TestServiceName",
}
tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
defer closer.Close()
if err != nil {
panic(err)
}
span := tracer.StartSpan("account_web")
defer span.Finish()
time.Sleep(1 * time.Second)
}
嵌套
parentSpan := tracer.StartSpan("order_web")
span1 := tracer.StartSpan("cart_srv", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(1 * time.Second)
span1.Finish()
span2 := tracer.StartSpan("product_srv", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(2 * time.Second)
span2.Finish()
span3 := tracer.StartSpan("stock_srv", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(3 * time.Second)
span3.Finish()
parentSpan.Finish()
Jaeger-client实现
提取 Extrace
- 为什么要提取:找到父亲
- 从哪里提取:进程内,不同进程之间各自约定
提取什么:
- traceid
- spanid
- parentid
- 是否采集
注入 Inject
- 为什么要注入:为了让孩子能找到爸爸
- 注入到哪里:和提取相对
- 注入了什么:和提取相对
异步report
- Span.finish
- 把Span放入队列
- 从队列取出,生成thrift,放入spanBuffer
- Flush到远程
低消耗
- 消耗在哪里:Jaeger-client作用于应用层,提取、注入、生成span、序列化成Thrift、发送到远程等,一系列操作这些都会带来性能上的损耗。
采集策略
- Constant
- Probabilistic 概率随机
- Rate Limiting
- Remote
应用透明
如何做到让业务开发人员无感知
- Golang:约定第一个参数为ctx,把parentSpan放入ctx
- PHP:使用全局变量
服务端实现
- Jaeger-agent负责上报数据的整理
- Jaeger-collector负责数据保存
- Jaeger-query负责数据查询
- Jaeger-agent和Jaeger-collector使用基于TCP协议实现的RPC进行通讯
Jaeger-agent
- 监听3个UDP端口
- 接收Jaeger-client的数据,放入队列dataChan
- 从队列dataChan获取数据,进行校验
- 提交数据
Jaeger-collector 源码阅读
- 协程池:启动时划分固定数量的协程
- 接收jaeger-agent数据
- 放入队列
- 从队列拿出来,写入数据库