当代的互联网的服务,通常都是用复杂的、大规模分布式集群来实现的。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具。
1.如何快速发现问题?
2.如何判断故障影响范围?
3.如何梳理服务依赖以及依赖的合理性?
4.如何分析链路性能问题以及实时容量规划?
分布式服务的跟踪系统需要记录在一次特定的请求后系统中完成的所有工作的信息,为服务器上每一次你发送和接收动作来收集跟踪标识符(message identifiers)和时间戳(timestamped events)。
OpenTracing是一个跨编程语言的标准,OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。
Span表示跨度,可以理解成一次方法调用, 一个程序块的调用或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个Span。
Ex:一条Trace由8个Span组成
Span因果关系
Span时间关系
Trace: 一个由多个Span组成的有向无环图(DAG图)
Span: 代表系统中具有开始时间和执行时长的逻辑运行单元
References: Span与Span的关系,关系包括 ChildOf和FollowsFrom两类
Operation name: Span的操作名称
Start Timestamp: Span的起始时间
Finish Timestamp: Span的结束时间
Span Tag: 一组键值对构成的Span标签集合
Span Log: 一组span的日志集合
SpanContext: Span上下文对象,代表跨越进程边界,传递到下级span的状态。
Baggage: 存储在SpanContext中的一个键值对(SpanContext)集合。它会在一条追踪链路上的所有span内全局传输
Inject and Extract: SpanContexts可以通过Injected操作向Carrier增加,或者通过Extracted从Carrier中获取,跨进程通讯数据(例如:HTTP头)
国内外现有比较成熟的链路追踪系统包括 Dapper(Google)、Cat(美团)、Zipkin(Twitter)、Pinpoint、Skywalking、Jaeger(Uber)
Jaeger 是一个开源的分布式追踪平台,可用来对现代的、云原生 的基于微服务的应用程序中组件间的交互进行监控、创建网络配置集并进行故障排除。
使用 Jaeger 可让您执行以下功能:
监控分布式事务
优化性能和延迟时间
执行根原因分析
1.与 Kiali 集成 – 当正确配置时,您可以从 Kiali 控制台查看 Jaeger 数据
2.高可伸缩性 – Jaeger 后端被设计为没有单一故障点,并可根据需要进行缩放。
3.分布式上下文发布 – 允许您通过不同的组件连接数据以创建完整的端到端的 trace。
4.与 Zipkin 的后向兼容性 – Jaeger 带有 API,让它可以作为 Zipkin 的直接替代项
Jaeger Client:Jaeger client 是 OpenTracing API 的具体语言实现
Jaeger Agent :Jaeger 代理是一个网络守护进程,它会监听通过 User Datagram Protocol (UDP) 发送的 Span,并发送到收集程序。
Jaeger Collector :与代理类似,该收集器可以接收 Span,并将其放入内部队列以便进行处理。这允许收集器立即返回到客户端/代理,而不需要等待 Span 进入存储。
Storage(Data Store):收集器需要一个持久的存储后端。可以是ElasticSearch或者Cassandra
Query:Query 是一个从存储中检索 trace 的服务
大家可以自行度娘(略)
Jaeger支持Docker安装和二进制安装。本文以二进制文件安装为例
1.官网下载二进制文件 https://www.jaegertracing.io/download/
2.解压压缩包,会看到如下文件
example-hotrod jaeger-agent jaeger-all-in-one jaeger-collector jaeger-ingester jaeger-query
3.启动组建
可以直接启动jaeger-all-in-one 完成所有组件的启动。为了方便查看各组件配置,我们采用分别启动,启动顺序jaeger-collector --> jaeger-agent
./jaeger-collector --span-storage.type=elasticsearch --es.server-urls=http://10.190.33.138:9200
注:jaeger-collector启动端口可能存在变更。
./jaeger-agent --reporter.grpc.host-port=10.190.33.138:14250 --reporter.grpc.discovery.min-peers=1
./jaeger-query --span-storage.type=elasticsearch --es.server-urls=http://10.190.33.138:9200
通过以上配置可以清晰的看到组建间各自的关系,至此简单的Jaeger系统搭建完成
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"log"
)
//jaeger-agent 默认开启端口
const AgentHost = "10.190.33.138:6831"
//设置GlobalTracer
var TraceCloser io.Closer
func InitTracer(appName string) (err error) {
cfg := config.Configuration{
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: AgentHost,
},
}
TraceCloser, err = cfg.InitGlobalTracer(appName)
if err != nil {
return err
}
return nil
}
//最后需要手动关闭,因为client发送span信息到jaeger-agent是异步发送,直接关闭程序可能会丢失数据。
func Closer() {
TraceCloser.Close()
}
//编写middleware拦截器从http header中进行拦截
func Trace() gin.HandlerFunc {
return func(c *gin.Context) {
tracer := opentracing.GlobalTracer()
var span opentracing.Span
//从http header中进行span拦截
spanCtx, err := tracer.Extract(opentracing.HTTPHeaders,opentracing.HTTPHeadersCarrier(c.Request.Header))
if err != nil {
//未拦截到,当前span作为root Span
span = opentracing.StartSpan(c.Request.URL.Path)
//Finish 将span发送给jaeger-agent
defer span.Finish()
} else {
//拦截到,定义当前Span与header作为父子Span
span = opentracing.StartSpan(c.Request.URL.Path,opentracing.ChildOf(spanCtx))
defer span.Finish()
}
log.Println(c.Request.Header)
//将Span信息注入到gin.context中,供后续使用
c.Set("ctx-span", span)
c.Next()
}
}
//从gin.context获取Span 信息
func GetSpanFromContext(ctx *gin.Context) (opentracing.Span, bool) {
spanFromCtx, exists := ctx.Get("ctx-span")
if exists {
span, ok := spanFromCtx.(opentracing.Span)
if ok {
return span, true
}
}
return nil, false
}
//服务内部Span传递,比如调用Mysql、redis、或者某些函数等调用
func Mysql(ctx *gin.Context) interface{} {
parentSpan, ok := jaeger.GetSpanFromContext(ctx)
var span opentracing.Span
if ok {
//parent span --> child span
span = opentracing.StartSpan("Mysql",opentracing.ChildOf(parentSpan.Context()))
} else {
//no parent span --> root span
span = opentracing.StartSpan("Mysql")
}
defer span.Finish()
//do sth
time.Sleep(time.Second * 3)
return ""
}
//注入,将span信息注入到http header中用于span信息传递(以封装Get方法为例)
func Get(ctx *gin.Context,url string) (string,error) {
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
span, ok := jaeger.GetSpanFromContext(ctx)
if ok {
log.Println(span)
err = opentracing.GlobalTracer().Inject(span.Context(),opentracing.HTTPHeaders,opentracing.HTTPHeadersCarrier(req.Header))//使用HTTPHeadersCarrier
if err != nil {
log.Println(err)
}
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
result, _ := ioutil.ReadAll(resp.Body)
return string(result), nil
}
结果查看
Jaeger Query 自带UI,默认端口16686
访问查看http://10.190.33.138:16686/search
主界面
选择Service 点击Find Traces (注意查看搜索范围)
能看到所有的调用trace,点击其中某一个
切换调用拓扑图
1.Span在gin.Context的数据格式
通过打印Span信息
2021/07/05 15:25:21 1ecc68d0084d8a17:1ecc68d0084d8a17:0000000000000000:1
2021/07/05 15:25:15 1ecc68d0084d8a17:4b74e9882ebc1b27:1ecc68d0084d8a17:1
2021/07/05 15:25:15 1ecc68d0084d8a17:675653a549918d7a:4b74e9882ebc1b27:1
可以看出,Span由几个信息组成 traceid:spanid:parentspanid:flag组成 flag决定日志的采样率
2.Span在http header中数据格式
通过打印header信息
2021/07/05 15:25:21 map[Accept-Encoding:[gzip] Uber-Trace-Id:[1ecc68d0084d8a17:1ecc68d0084d8a17:0000000000000000:1] User-Agent:[Go-http-client/1.1]]
可以看出,其是定义Key Uber-Trace-Id 将Span信息设置成value
3.ES中数据结构
查看ES索引,可以看到会相应生成两个索引
jaeger-service-2021-xx-xx 索引
jaeger-span-2021-xx-xx 索引
可以看出,jaeger将service信息和span信息分开存储。
jaeger-service 索引中数据
{
"_index": "jaeger-service-2021-06-30",
"_type": "_doc",
"_id": "e322a9972f09925a",
"_version": 1,
"_score": 0,
"fields": {
"operationName": [
"/serverA/to/serverB"
],
"serviceName": [
"quick-gin"
]
}
}
数据含义显而易见,不需要过多说明
能看出references保存着相关parent的span信息,duration存储耗时信息,也就是span定义到Finish的时间。
参考链接:
OpenTracing文档中文版 https://wu-sheng.gitbooks.io/opentracing-io/content/
Jaeger官网 https://www.jaegertracing.io/