jaeger安装与测试
jaeger简介
Jaeger 受 Dapper 和 OpenZipkin 的启发,是由 Uber Technologies 发布的开源分布式追踪系统。它用于监控和排查
基于微服务的分布式系统问题,包括:
- 分布式上下文传播
- 分布式事务监控
- 根因分析
- 服务依赖关系分析
- 性能 / 延迟优化
Jaeger 架构:
整体上讲,一个基础的 Jaeger 追踪系统包含下面几个部分:
- jaeger-query: 用于客户端查询和检索组件,并包含了一个基础的 UI
- jaeger-collector: 接收来自 jaeger-agent 的 trace 数据,并通过处理管道来执行。当前的处理管道包含验证 trace 数据,创建索引,执行数据转换以及将数据存储到对应的后端
- jaeger-agent: 一个网络守护进程,侦听通过 UDP 发送的 spans ,它对其进行批处理并发送给收集器。它被设计为作为基础设施组件部署到所有主机。代理将收集器的路由和发现从客户机抽象出来
- backend-storage: 用于指标数据存储的可插拔式后端存储,支持 Cassandra, Elasticsearch and Kafka
- ingester: 可选组件,用于从 kafka 中消费数据并写入到可直接读取的 Cassandra 或 Elasticsearch 存储中
注意:
trace 和 span 是链路追踪系统中的专业属于,span 就表示具体的一个工作单元,并且包含了该单元的元数据比如具体是什么操作,执行的时间等等,而 trace 是通过整个系统的数据路径,可以被认为是将 Spans 组成一个有向无环图 (DAG)
而对于一个多组件系统来说,也存在多个网络端口用于数据的传输,下图即为各个组件的端口用途:
安装
# 安装docker
apt install docker.io -y
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 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
访问http://10.1.1.70:16686/search查看页面
编写示例trace代码
要在 Golang 微服务中集成 Jaeger 进行分布式跟踪,您需要使用 OpenTracing API 和 Jaeger 的 Golang 客户端库。以下是三个微服务的示例,它们通过 Jaeger 进行跟踪。请注意,您需要先安装 github.com/uber/jaeger-client-go
和 github.com/opentracing/opentracing-go
库。
server1 - 服务 A:
package main
import (
"io/ioutil"
"log"
"net/http"
"time"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func main() {
// 初始化 Jaeger tracer
cfg := config.Configuration{
ServiceName: "serviceA",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "localhost:6831", // Jaeger Agent 的地址
},
}
tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
if err != nil {
log.Fatalf("Could not initialize jaeger tracer: %s", err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
// Define HTTP handler for serviceA
http.HandleFunc("/serviceA", func(w http.ResponseWriter, r *http.Request) {
// 开始一个新的 span
span := tracer.StartSpan("/serviceA")
defer span.Finish()
// 发送HTTP请求到服务B
req, _ := http.NewRequest("GET", "http://localhost:8082/serviceB", nil)
// 将 span 的 context 注入到 HTTP 请求头中
err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if err != nil {
log.Fatalf("Could not inject span context into header: %s", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Simulate some processing
time.Sleep(45 * time.Millisecond)
// 读取服务B的响应
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 将服务B的响应写入服务A的响应体
w.Write(body)
})
log.Fatal(http.ListenAndServe(":8081", nil))
}
server2 - 服务 B:
package main
import (
"io/ioutil"
"log"
"net/http"
"time"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func main() {
// 初始化 Jaeger tracer
cfg := config.Configuration{
ServiceName: "serviceB",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "localhost:6831", // Jaeger Agent 的地址
},
}
tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
if err != nil {
log.Fatalf("Could not initialize jaeger tracer: %s", err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
// Define HTTP handler for serviceB
http.HandleFunc("/serviceB", func(w http.ResponseWriter, r *http.Request) {
// 从 HTTP 请求头中提取父 span 的 context
parentSpanContext, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
// 创建一个新的 span 作为父 span 的子 span
span := tracer.StartSpan("/serviceB", opentracing.ChildOf(parentSpanContext))
defer span.Finish()
// 发送HTTP请求到服务B
req, _ := http.NewRequest("GET", "http://localhost:8083/serviceC", nil)
// 将 span 的 context 注入到 HTTP 请求头中
err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if err != nil {
log.Fatalf("Could not inject span context into header: %s", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Simulate some processing
time.Sleep(45 * time.Millisecond)
// 读取服务B的响应
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 将服务B的响应写入服务A的响应体
w.Write(body)
})
log.Fatal(http.ListenAndServe(":8082", nil))
}
server3 - 服务 C:
package main
import (
"io/ioutil"
"log"
"net/http"
"time"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func main() {
// 初始化 Jaeger tracer
cfg := config.Configuration{
ServiceName: "serviceC",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "localhost:6831", // Jaeger Agent 的地址
},
}
tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
if err != nil {
log.Fatalf("Could not initialize jaeger tracer: %s", err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
// Define HTTP handler for serviceB
http.HandleFunc("/serviceC", func(w http.ResponseWriter, r *http.Request) {
// 从 HTTP 请求头中提取父 span 的 context
parentSpanContext, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
// 创建一个新的 span 作为父 span 的子 span
span := tracer.StartSpan("/serviceC", opentracing.ChildOf(parentSpanContext))
defer span.Finish()
// 发送HTTP请求到服务B
req, _ := http.NewRequest("GET", "http://ifconfig.me", nil)
// 将 span 的 context 注入到 HTTP 请求头中
err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if err != nil {
log.Fatalf("Could not inject span context into header: %s", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Simulate some processing
time.Sleep(45 * time.Millisecond)
// 读取服务B的响应
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 将服务B的响应写入服务A的响应体
w.Write(body)
})
log.Fatal(http.ListenAndServe(":8083", nil))
}
在这些示例中,每个服务都会创建一个新的 span,并在发送 HTTP 请求时将 span 的 context 注入到请求头中。接收服务会从请求头中提取 span context,并创建一个新的 span 作为子 span。这样,Jaeger 就可以跟踪整个请求链路。
启动测试程序
screen -dmS srv1 /root/test/srv1 &
screen -dmS srv2 /root/test/srv2 &
screen -dmS srv3 /root/test/srv3 &
访问serviceA
curl http://localhost:8081/serviceA
查看jaeger面板
FAQ
jaeger trace应用同样需要插入代码吗
是的,要在应用程序中使用 Jaeger 进行跟踪,您需要在代码中插入一些代码来启动和记录跟踪信息。以下是使用 Jaeger 的一般步骤:
-
添加 Jaeger 客户端库:
- 根据您的编程语言和框架,添加适当的 Jaeger 客户端库。这些库允许您创建和记录跟踪数据。
- 例如,在 Java 中,您可以使用
io.opentracing
库。
-
初始化 Jaeger Tracer:
- 在应用程序的启动代码中,初始化 Jaeger Tracer。这通常涉及设置连接到 Jaeger 后端的配置,例如 Jaeger Collector 的地址。
-
创建和记录 Spans:
- 在应用程序的关键代码路径中,创建和记录 spans。
- 每个 span 表示一个操作或事件,例如处理 HTTP 请求、数据库查询等。
- 您可以使用
startSpan
方法来创建 span,并使用finish
方法来结束它。
-
将 Trace 数据发送到 Jaeger 后端:
- 在每个 span 结束时,将 trace 数据发送到 Jaeger 后端。
- 这通常是异步的,以避免对应用程序性能产生太大的影响。
-
查看 Jaeger UI:
- 启动 Jaeger UI,您可以在其中查看跟踪数据、服务依赖关系和其他相关信息。
总之,要使用 Jaeger 进行跟踪,您需要在应用程序中插入代码来创建、记录和发送跟踪数据。这样,您就可以在 Jaeger UI 中查看分布式调用链的详细信息。🔍
jaeger会发送metrics吗
是的,Jaeger 会发送指标(metrics)。默认情况下,Jaeger 的微服务以 Prometheus 格式公开指标。这些指标可以用于监控和性能分析。以下是一些与 Jaeger 监控和指标相关的信息:
-
Prometheus 格式的指标:
- Jaeger 的各个组件(如 jaeger-agent、jaeger-collector、jaeger-query 等)都会在管理端口上公开 Prometheus 格式的指标。
- 您可以通过访问
/metrics
路径来获取这些指标。
-
Jaeger 的监控 mixin:
- Jaeger 提供了一个 Prometheus 监控 mixin,用于帮助人们使用 Prometheus、Alertmanager 和 Grafana 监控 Jaeger。
- 这包括一个预构建的仪表盘,用于可视化 Jaeger 的性能指标。
-
Jaeger 的日志:
- Jaeger 组件只记录到标准输出,使用结构化日志库
go.uber.org/zap
配置为将日志行写入 JSON 编码的字符串。
- Jaeger 组件只记录到标准输出,使用结构化日志库
总之,Jaeger 会生成 Prometheus 格式的指标,您可以使用 Prometheus、Grafana 等工具来监控和分析这些指标。🔍
如果您想深入了解 Jaeger 的监控和指标,可以查阅 Jaeger 的官方文档。