jeager基本知识
数据格式说明
服务之间调用时,为了确保各自创建的span能串成一个调用链,那么服务之间需要传递trace和span相关信息。jaeger规范了信息的 格式,有提供默认方式,也支持自定义。
具体的说明,可以看官方文档,https://www.jaegertracing.io/docs/1.39/client-libraries/#propagation-format
对于客户端包用的,https://github.com/jaegertracing/jaeger-client-go, 里面也有使用说明。
1、trace/span 信息
数据的key为: uber-trace-id,
数据的value说明: {trace-id}:{span-id}:{parent-span-id}:{flags}
例子 5d936af3ff3599c6:2f5a9dee5abd7978:5d936af3ff3599c6:1
{trace_id}
- 64位或者128位随机数字,用base16编码。
- 值为0是无效数据
{span_id}
- 64位随机数字,用base16编码。
- 值为0是无效数据
{parent-span-id}
- 64位随机数字,用base16编码。代表父span的id。
- 值为0是有效的,代表当前是“根span”。
{flags}
- 1个字节的位图。
- 1代表这个trace被采样采纳。
- 0代表这个trace不被采纳。
- 2代表这个是“debug”标识。
2、Baggage
数据的key: uberctx-{baggage-key} 数据的value: {baggage-value} 字符串。
例子:
span.SetBaggageItem("key1", "value1")
span.SetBaggageItem("key2", "value2")
在http请求的header中能看到如下内容:
uberctx-key1: value1
uberctx-key2: value2
opentracing包
如果A服务通过http调用B服务,A中发送的header要注入jaeger链路信息。
想要把jaeger链路信息发送到下一个服务,就要通过header中传递数据。我们只需要把本服务当前span拿到,把它的信息注入到要发送的header中即可,jaeger提供了tracer.Inject()方法,参数就是span的上下文,数据格式,存数据的header。
如下,从当前ctx中拿到span后,把span的context()注入搭配请求的header中。
func makeSomeRequest(ctx context.Context) ... {
if span := opentracing.SpanFromContext(ctx); span != nil {
httpClient := &http.Client{}
httpReq, _ := http.NewRequest("GET", "http://myservice/", nil)
// Transmit the span's TraceContext as HTTP headers on our
// outbound request.
opentracing.GlobalTracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(httpReq.Header))
resp, err := httpClient.Do(httpReq)
...
}
...
}
B服务拿到http请求,要从header中取到数据。如下:
从header中抽取需要的数据,如果存在就用于创建span。
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
var serverSpan opentracing.Span
appSpecificOperationName := ...
wireContext, err := opentracing.GlobalTracer().Extract(
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header))
if err != nil {
// Optionally record something about err here
}
// Create the span referring to the RPC client if available.
// If wireContext == nil, a root span will be created.
serverSpan = opentracing.StartSpan(
appSpecificOperationName,
ext.RPCServerOption(wireContext))
defer serverSpan.Finish()
ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
...
}
启动一个服务
本地运行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 \
--restart=always \
jaegertracing/all-in-one:1.15
ui的访问方式:http://127.0.0.1:16686/search
gin框架项目demo_v2
1、jaeger初始化
在项目目录下,创建 jaeger/init.go ,里面提供初始化iaeger服务的方法 StartJaeger()
package jaeger
import (
"fmt"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go/config"
"io"
"io/ioutil"
"log"
)
var JaegerUrl = "127.0.0.1"
var jaegerPort = 6831
func StartJaeger(serviceName string) {
tracer, _ := initJaeger(serviceName)
opentracing.SetGlobalTracer(tracer)
}
func initJaeger(serviceName string) (opentracing.Tracer, io.Closer) {
if JaegerUrl == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
cfg := &config.Configuration{
ServiceName: serviceName,
Sampler: &config.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &config.ReporterConfig{
LocalAgentHostPort: fmt.Sprintf("%s:%d", JaegerUrl, jaegerPort),
LogSpans: true,
},
}
tracer, closer, err := cfg.NewTracer()
if err != nil {
log.Fatalf("Failed to init Jaeger client: %s", err)
}
return tracer, closer
}
2、gin的middleware
在项目目录下,创建 jaeger/gin.go ,里面提gin框架用的middleware
它从header中抽取到链路追踪的context,然后基于它创建span。可能是一个新的span,也可能是和web调用方的链路连在一起的span。再把这个span的ctx放到gin.Context中给后续用。
package jaeger
import (
"context"
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"net/http"
)
var (
SpanCTX = "span-ctx"
)
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
tracer := opentracing.GlobalTracer()
var span opentracing.Span
// 从header中获取jaeger信息
wireContext, err := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
// 如果失败,构建新的span
if err != nil {
span = opentracing.StartSpan(c.FullPath())
} else {
span = opentracing.StartSpan(c.FullPath(), ext.RPCServerOption(wireContext))
}
defer span.Finish()
c.Set(SpanCTX, opentracing.ContextWithSpan(c, span))
c.Next()
}
}
3、gin的server1例子
目录文件 v2/http_server1/main.go
- 这是一个gin的web服务,监听8001端口,提供了一个“/hello”接口服务。
- 浏览器访问8001的hello,服务会调用8002的服务。
- 这个hello的handler中,它会从gin.Context中取到span的ctx,这个span的名称就是http收到路由,然后基于它创建span,名称随意,可以存一些信息等。
- 调用8002服务之前,把span上下文取出来注入到http请求中。
package main
import (
"context"
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"io/ioutil"
"log"
"net/http"
jaeger2 "swwgo/basic/use_jaeger/v2/jaeger"
"time"
)
func main() {
jaeger2.StartJaeger("http_server1")
r := gin.New()
r.Use(jaeger2.Middleware())
r.GET("/hello", func(c *gin.Context) {
time.Sleep(time.Second * 1)
// 再创建一个span
spanC, _ := c.Get(jaeger2.SpanCTX)
spanCtx := spanC.(context.Context)
subSpan, _ := opentracing.StartSpanFromContext(spanCtx, "asdsad")
defer subSpan.Finish()
subSpan.SetBaggageItem("good_key1", "good_value1")
subSpan.SetBaggageItem("good_key2", "good_value2")
// 构建GET请求
client := &http.Client{}
request, _ := http.NewRequest("GET", "http://127.0.0.1:8002/hello", nil)
// 把trace信息传到下一个http服务
opentracing.GlobalTracer().Inject(subSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))
resp, err := client.Do(request)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}
c.JSON(200, gin.H{
"code" : 0,
"msg" : string(body),
})
})
s := &http.Server{
Addr: ":8001",
Handler: r,
}
err := s.ListenAndServe()
if err != nil {
log.Panicf("start http failed: %v", err)
}
}
4、gin的server2例子
目录文件 v2/http_server2/main.go
- 这个server2比较简单,就是提供/hello接口提供服务,由middleware来构建追踪的链路。
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
jaeger2 "swwgo/basic/use_jaeger/v2/jaeger"
)
func main() {
jaeger2.StartJaeger("http_server2")
r := gin.New()
r.Use(jaeger2.Middleware())
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"code" : 0,
"msg" : "I am http-server2",
})
})
s := &http.Server{
Addr: ":8002",
Handler: r,
}
err := s.ListenAndServe()
if err != nil {
log.Panicf("start http failed: %v", err)
}
}
gin框架项目demo_v3
1、jaeger初始化
和demo_v2一样 jaeger/init.go内容一致。
2、gin的middleware
在项目目录下,创建 v3/jaeger/gin.go ,里面提gin框架用的middleware
package jaeger
import (
"fmt"
"github.com/gin-gonic/gin"
kitot "github.com/go-kit/kit/tracing/opentracing"
"github.com/opentracing/opentracing-go"
)
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 从header中拿到链路的spanCtx, 拿到则创建的span与之前链路打通的,否则是root span
f := kitot.HTTPToContext(opentracing.GlobalTracer(), c.FullPath(), new(myLog))
ctx := f(c, c.Request)
// 拿到span,函数结束时调用finish
span := opentracing.SpanFromContext(ctx)
defer span.Finish()
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
type myLog struct {}
func (l *myLog) Log(keyvals ...interface{}) error {
fmt.Println(keyvals)
return nil
}
上面用到了库github.com/go-kit/kit/tracing/opentracing中的方法,如下
它是从http的request中抽取到链路追踪的上下文信息,基于这个能构建出span数据,再把这个span的上下文输出出来,用于后续的span使用。
// HTTPToContext returns an http RequestFunc that tries to join with an
// OpenTracing trace found in `req` and starts a new Span called
// `operationName` accordingly. If no trace could be found in `req`, the Span
// will be a trace root. The Span is incorporated in the returned Context and
// can be retrieved with opentracing.SpanFromContext(ctx).
func HTTPToContext(tracer opentracing.Tracer, operationName string, logger log.Logger) kithttp.RequestFunc {
return func(ctx context.Context, req *http.Request) context.Context {
// Try to join to a trace propagated in `req`.
var span opentracing.Span
wireContext, err := tracer.Extract(
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header),
)
if err != nil && err != opentracing.ErrSpanContextNotFound {
logger.Log("err", err)
}
span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext))
ext.HTTPMethod.Set(span, req.Method)
ext.HTTPUrl.Set(span, req.URL.String())
return opentracing.ContextWithSpan(ctx, span)
}
}
3、gin的server1例子
目录文件 v3/http_server1/main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"io/ioutil"
"log"
"net/http"
jaeger3 "swwgo/basic/use_jaeger/v3/jaeger"
"time"
)
func main() {
jaeger3.StartJaeger("http_server1")
r := gin.New()
r.Use(jaeger3.Middleware())
r.GET("/hello", func(c *gin.Context) {
time.Sleep(time.Second * 1)
// 再创建一个span
span, _ := opentracing.StartSpanFromContext(c.Request.Context(), "handler hello")
defer span.Finish()
span.SetBaggageItem("good_key1", "good_value1")
span.SetBaggageItem("good_key2", "good_value2")
// 构建GET请求
client := &http.Client{}
request, _ := http.NewRequest("GET", "http://127.0.0.1:8002/hello", nil)
// 把trace信息传到下一个http服务
opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))
resp, err := client.Do(request)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}
c.JSON(200, gin.H{
"code" : 0,
"msg" : string(body),
})
})
s := &http.Server{
Addr: ":8001",
Handler: r,
}
err := s.ListenAndServe()
if err != nil {
log.Panicf("start http failed: %v", err)
}
}
4、gin的server2例子
目录文件 v3/http_server2/main.go
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
jaeger3 "swwgo/basic/use_jaeger/v3/jaeger"
)
func main() {
jaeger3.StartJaeger("http_server2")
r := gin.New()
r.Use(jaeger3.Middleware())
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"code" : 0,
"msg" : "I am http-server2",
})
})
s := &http.Server{
Addr: ":8002",
Handler: r,
}
err := s.ListenAndServe()
if err != nil {
log.Panicf("start http failed: %v", err)
}
}