jaeger分布式链路追踪
推荐去官网系统的了解一下https://www.jaegertracing.io随着应用容器化和微服务的兴起,借由Docker和 Kubernetes 等工具, 服务的快速开发和部署成为可能,构建微服务应用变得越来越简单。但是随着大型单体应用拆分为微服务,服务之间的依赖和调用变得极为复杂,这些服务可能是不同团队开发的,可能基于不同的语言,微服务之间可能是利用RPC, RESTful API, 也可
推荐去官网系统的了解一下 https://www.jaegertracing.io
随着应用容器化和微服务的兴起,借由Docker和 Kubernetes 等工具, 服务的快速开发和部署成为可能,构建微服务应用变得越来越简单。但是随着大型单体应用拆分为微服务,服务之间的依赖和调用变得极为复杂,这些服务可能是不同团队开发的,可能基于不同的语言,微服务之间可能是利用RPC, RESTful API, 也可能是通过消息队列实现调用或通讯。如何理清服务依赖调用关系,如何在这样的环境下快速debug, 追踪服务处理耗时,查找服务性能瓶颈, 合理对服务的容量评估都变成一个棘手的事情。
Tracing在微服务中的作用
和传统单体服务不同, 微服务通常部署在一个分布式的系统中, 并且一个请求可能会经过好几个微服务的处理, 这样的环境下错误和性能问题就会更容易发生, 所以观察(Observe)尤为重要,
这就是Tracing的用武之地, 它收集调用过程中的信息并可视化, 让你知道在每一个服务调用过程的耗时等情况, 以便及早发现问题.
在上图可以看到api层一共花了4.03s, 然后其中调用其他服务: 'service-1'花了2.12s, 而service-1又调用了'service-2'花费了2.12s, 用这样的图示很容易就能排查到系统存在的问题. 在这里我只展示了时间, 如果需要追踪其他信息(如错误信息)也是可以实现的.
为什么是Jaeger
支持 OpenTracing
的 server
端有很多,我们总要选一个 。在这里,选用 jaeger
。 jaeger
的开发较为活跃,支持的客户端实现也较多。由于采用了 golang
开发,发行包也比较简洁。
jaeger的官网是 www.jaegertracing.io/
特点
- jaeger的开发语言是
golang
- jaeger支持OpenTracing协议,同属于CNCF基金会
- jaeger支持各种各样的客户端,包括Go、Java、Node、Python、C++等
- jaeger支持udp协议传输,当然也支持http
jaeger能够解决以下问题
- 分布式事务监控
- 性能分析与性能优化
- 调用链,找到根源问题
- 服务依赖分析(需大数据分析)
安装需了解的技术栈:
- OpenTracing
- Golang
- ElasticSearch
- Kafka (可选)
OpenTracing 标准
云原生基金会(CNCF) 推出了 OpenTracing 标准,推进Tracing协议和工具的标准化, 统一 Trace 数据结构和格式。 OpenTracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。比如从Zipkin替换成Jaeger/Skywalking等后端。
在OpenTracing中,有两个主要概念:
1、Trace(调用链): OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。
2、Span(跨度):可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问. 只要是一个具有完整时间周期的程序访问,都可以被认为是一个Span。
单个Trace中,Span间的因果关系如下图:
这里使用目前比较流行的Tracing开源方案Jaeger进行实践,使用jaeger-client-go这个库作为client
github地址:GitHub - jaegertracing/jaeger-client-go: Jaeger Bindings for Go OpenTracing API.
开放分布式追踪(OpenTracing)入门与 Jaeger 实现
jaeger架构
jaeger组件介绍:
jaeger-client:jaeger 的客户端,实现了opentracing协议;
jaeger-agent:jaeger client的一个代理程序,client将收集到的调用链数据发给agent,然后由agent发给collector;
jaeger-collector:负责接收jaeger client或者jaeger agent上报上来的调用链数据,然后做一些校验,比如时间范围是否合法等,最终会经过内部的处理存储到后端存储;
jaeger-query:专门负责调用链查询的一个服务,有自己独立的UI;
jaeger-ingester:中文名称“摄食者”,可用从kafka读取数据然后写到jaeger的后端存储,比如Cassandra和Elasticsearch;
spark-job:基于spark的运算任务,可以计算服务的依赖关系,调用次数等;
其中jaeger-collector和jaeger-query是必须的,其余的都是可选的,我们没有采用agent上报的方式,而是让客户端直接通过endpoint上报到collector。
官方文档的demo:example
首先,本地起一个jaeger服务作为测试用的服务端,官方提供了”All in One”的docker镜像, 启动Jaeger服务只需要一行代码:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:1.12
本人使用下载好的golang二进制文件启动的,jaeger官网地址:https://www.jaegertracing.io/downlo
jaeger的二进制发行包包含五个二进制文件:
- jaeger-agent
- jaeger-collector
- jaeger-query
- jaeger-standalone
- jaeger-ingester
如果没有执行权限,可以使用
- chmod a+x jaeger-*
选择存储
trace数据总要存在一个地方。jaeger支持 ES
和 Canssandra
两种后端DB。国内用ES的多一点,我们就以ES为例,来介绍其安装方式。
ES请先自行安装。
由于上面四个命令都有很多参数,所以我们可以创建几个脚本,来支持jaeger的启动。start-collector.sh
- export SPAN_STORAGE_TYPE=elasticsearch
- nohup ./jaeger-collector --es.server-urls http://10.66.177.152:9200/ --log-level=debug > collector.log 2>&1 &
start-agent.sh
- export SPAN_STORAGE_TYPE=elasticsearch
- nohup .\jaeger-agent.exe --reporter.grpc.host-port=192.168.1.234:14250 --log-level=debug > agent.log 2>&1 &
start-query.sh
- export SPAN_STORAGE_TYPE=elasticsearch
- nohup ./jaeger-query --span-storage.type=elasticsearch --es.server-urls=http://10.66.177.152:9200/ > query.log 2>&1 &
部署方式
jaeger有两种部署方式。下面一一介绍。如果你的数据量特别多,使用kafka缓冲一下也是可以的(所以就引入了另外一个组件jaeger-ingester),不多做介绍。
简易环境
这种方式一般用在dev环境或者其他测试环境。只需要部署一个单一节点即可。我们的app,需要手动填写agent的地址,这个地址一般都是固定的。
这些环境的流量很小,一个agent是足够的。
生产环境
上面这种部署方式,适合生产环境。agent安装在每一台业务机器上。Client端的目标agent只需要填写localhost即可。
这种方式的好处是生产环境的配置非常的简单。即使你的机器是混合部署的,也能正常收集trace信息。
调用关系图
jaeger的调用关系图是使用spark任务进行计算的。项目地址为:
https://github.com/jaegertracing/spark-dependencies
端口整理
Agent
- 5775 UDP协议,接收兼容zipkin的协议数据
- 6831 UDP协议,接收兼容jaeger的兼容协议
- 6832 UDP协议,接收jaeger的二进制协议
- 5778 HTTP协议,数据量大不建议使用
它们之间的传输协议都是基于thrift封装的。我们默认使用5775作为传输端口
Collector
- 14267 tcp agent发送jaeger.thrift格式数据
- 14250 tcp agent发送proto格式数据(背后gRPC)
- 14268 http 直接接受客户端数据
- 14269 http 健康检查
Query
- 16686 http jaeger的前端,放给用户的接口
- 16687 http 健康检查
至此,我们的jaeger就安装完毕。
以上,就是我们的环境准备。有了一个server接收数据,调用链的主要工作就在于客户端开发
接下来,代码时间, 参考项目的Readme和搜索引擎不难写出以下代码
1,最简单的使用模式
2,多个函数之间调用
3,http请求
clinet中同步请求:8081/format,:8088/publish
package main
import (
"fmt"
"context"
"github.com/opentracing/opentracing-go/ext"
"io/ioutil"
"net/http"
opentracing "github.com/opentracing/opentracing-go"
jaeger "github.com/uber/jaeger-client-go"
config "github.com/uber/jaeger-client-go/config"
"github.com/opentracing/opentracing-go/log"
"io"
"net/url"
)
// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
cfg := &config.Configuration{
Sampler:&config.SamplerConfig{
Type: "const",
Param:1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
//LocalAgentHostPort: "192.168.1.234:6831",
LocalAgentHostPort: "192.168.2.246:6831",
},
}
tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("Error: connot init Jaeger: %v\n", err))
}
return tracer, closer
}
func main() {
tracer, closer := initJaeger("http-demo-client")
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
span := tracer.StartSpan("say-hello")
span.SetTag("hello-to", "helloTo")
defer span.Finish()
ctx := opentracing.ContextWithSpan(context.Background(), span)
helloStr := formatString(ctx, "helloTo")
printHello(ctx, helloStr)
fmt.Println("exit")
}
func formatString(ctx context.Context, helloTo string) string {
span, _ := opentracing.StartSpanFromContext(ctx, "formatString")
defer span.Finish()
v := url.Values{}
v.Set("helloTo", helloTo)
url := "http://localhost:8081/format?" + v.Encode()
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err.Error())
}
ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, url)
ext.HTTPMethod.Set(span, "GET")
span.Tracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header),
)
resp, err := httpDo(req)
if err != nil {
panic(err.Error())
}
helloStr := string(resp)
span.LogFields(
log.String("event", "string-format"),
log.String("value", helloStr),
)
return helloStr
}
func printHello(ctx context.Context, helloStr string) {
span, _ := opentracing.StartSpanFromContext(ctx, "printHello")
defer span.Finish()
v := url.Values{}
v.Set("helloStr", helloStr)
url := "http://localhost:8088/publish?" + v.Encode()
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err.Error())
}
ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, url)
ext.HTTPMethod.Set(span, "GET")
span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if _, err := httpDo(req); err != nil {
panic(err.Error())
}
}
func httpDo(req *http.Request) ([]byte, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("StatusCode: %d, Body: %s", resp.StatusCode, body)
}
return body, nil
}
package main
import (
"fmt"
"io"
"log"
"net/http"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
jaeger "github.com/uber/jaeger-client-go"
config "github.com/uber/jaeger-client-go/config"
)
// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
cfg := &config.Configuration{
Sampler:&config.SamplerConfig{
Type: "const",
Param:1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
//LocalAgentHostPort: "192.168.1.234:6831",
LocalAgentHostPort: "192.168.2.246:6831",
},
}
tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("Error: connot init Jaeger: %v\n", err))
}
return tracer, closer
}
func main() {
tracer, closer := initJaeger("http-formatter")
defer closer.Close()
http.HandleFunc("/format", func(w http.ResponseWriter, r *http.Request) {
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("format", ext.RPCServerOption(spanCtx))
defer span.Finish()
helloTo := r.FormValue("helloTo")
helloStr := fmt.Sprintf("Hello, I am format %s!", helloTo)
span.LogFields(
otlog.String("event", "string-format"),
otlog.String("value", helloStr),
)
w.Write([]byte(helloStr))
})
log.Fatal(http.ListenAndServe(":8081", nil))
}
formater/main.go
package main
import (
"fmt"
"io"
"log"
"net/http"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
jaeger "github.com/uber/jaeger-client-go"
config "github.com/uber/jaeger-client-go/config"
)
// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
cfg := &config.Configuration{
Sampler:&config.SamplerConfig{
Type: "const",
Param:1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
//LocalAgentHostPort: "192.168.1.234:6831",
LocalAgentHostPort: "192.168.2.246:6831",
},
}
tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("Error: connot init Jaeger: %v\n", err))
}
return tracer, closer
}
func main() {
tracer, closer := initJaeger("http-publish")
defer closer.Close()
http.HandleFunc("/publish", func(w http.ResponseWriter, r *http.Request) {
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("format", ext.RPCServerOption(spanCtx))
defer span.Finish()
helloTo := r.FormValue("helloTo")
helloStr := fmt.Sprintf("Hello, I am publish %s!", helloTo)
span.LogFields(
otlog.String("event", "string-publish"),
otlog.String("value", helloStr),
)
w.Write([]byte(helloStr))
})
log.Fatal(http.ListenAndServe(":8088", nil))
}
publish/main.go
更多推荐
所有评论(0)