golang[100]-jaeger

jaeger

1、jaeger是uber公司开发的一种链路追踪工具,是为了应对当前微服务的架构而设计。
2、jaeger是opentracing协议的一种实现,opentracing对链路追踪定义了协议。

要使用jaeger的第一步是搭建jager服务

docker安装jaeger server

1
2
3
4
5
6
7
docker run \
--rm \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 16686:16686 \
jaegertracing/all-in-one:1.7 \
--log-level=debug

UI界面

http://localhost:16686/

jaeger的使用

trace: 需要首先初始化trace,trace可以理解为span构成的有向无环图。
span: span是具体业务的一种抽象
A trace is a directed acyclic graph of spans.
A span is a logical representation of some work done in your application.
Each span has these minimum attributes: an operation name, a start time, and a finish time.

1
2
3
4
5
6
7
8
9
10
11
12
13
tp, error := jaeger.NewUDPTransport(cfg.JaegerUrl, 0)

if error != nil {
return errors.New("jaeger NewUDPTransport error")
}
tracer, close := jaeger.NewTracer(
serviceName,
jaeger.NewConstSampler(true),
jaeger.NewRemoteReporter(tp),
jaeger.TracerOptions.MaxTagValueLength(1024),
)
cl.AppendCloser(close)
opentracing.InitGlobalTracer(tracer)

可以通过多种方式创建jaeger,StartSpan(serviceName)创建一个root span
我们也可以通过下面的方法创建一个child span。child span 可以理解为父节点的子节点。

1
2
StartSpanFromContext、
opentracing.GlobalTracer().StartSpan(req.Method(), ext.RPCServerOption(spCtx))

jaeger要实现不同程序之间的链路关系,需要主动推送信息到server

In order to continue the trace over the process boundaries and RPC calls, we need a way to propagate the span context over the wire. The OpenTracing API provides two functions in the Tracer interface to do that, Inject(spanContext, format, carrier) and Extract(format, carrier).

format 和 carrier 就是附带的一些参数,这些参数会被下一个程序接收到。
format是这些参数的格式,在openntraceing中有3个标准的格式。
The format parameter refers to one of the three standard encodings the OpenTracing API defines:
TextMap where span context is encoded as a collection of string key-value pairs,
Binary where span context is encoded as an opaque byte array,
HTTPHeaders, which is similar to TextMap except that the keys must be safe to be used as HTTP headers.
carrier是附带的内容
The carrier is an abstraction over the underlying RPC framework. For example, a carrier for TextMap format is an interface that allows the tracer to write key-value pairs via Set(key, value) function, while a carrier for Binary format is simply an io.Writer.

Extract

1
2
carrier := opentracing.TextMapCarrier(md)
clientContext, err := tracer.Extract(opentracing.TextMap, carrier)

Inject

1
2
3
4
5
6
hash := make(map[string]string)
carrier := opentracing.TextMapCarrier(hash)
e := tracer.Inject(
sp.Context(),
opentracing.TextMap,
carrier)

在golang中,大量使用了context来进行操作。 在golang实现的jaeger客户端和opentracing中,需要注意span与context的大量api。
例如StartSpanFromContext会从context中读取父节点,并生成一个子节点。
在opentracing.StartSpan中

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"os"

"github.com/opentracing/opentracing-go/log"
"github.com/yurishkuro/opentracing-tutorial/go/lib/tracing"
)

func main() {
if len(os.Args) != 2 {
panic("ERROR: Expecting one argument")
}

tracer, closer := tracing.Init("hello-world")
defer closer.Close()

helloTo := os.Args[1]

span := tracer.StartSpan("say-hello")
span.SetTag("hello-to", helloTo)

helloStr := fmt.Sprintf("Hello, %s!", helloTo)
span.LogFields(
log.String("event", "string-format"),
log.String("value", helloStr),
)

println(helloStr)
span.LogKV("event", "println")

span.Finish()
}

2、子关系的span

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
"context"
"fmt"
"os"

"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/log"
"github.com/yurishkuro/opentracing-tutorial/go/lib/tracing"
)

func main() {
if len(os.Args) != 2 {
panic("ERROR: Expecting one argument")
}

tracer, closer := tracing.Init("hello-world")
defer closer.Close()
opentracing.SetGlobalTracer(tracer)

helloTo := os.Args[1]

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)
}

//StartSpanFromContext 建立一个span,是父亲节点的一个child。
// If we think of the trace as a directed acyclic graph where nodes are the spans and edges are the causal relationships between them, then the ChildOf option is used to create one such edge between span and rootSpan. In the API the edges are represented by SpanReference type that consists of a SpanContext and a label. The SpanContext represents an immutable, thread-safe portion of the span that can be used to establish references or to propagate it over the wire. The label, or ReferenceType, describes the nature of the relationship. ChildOf relationship means that the rootSpan has a logical dependency on the child span before rootSpan can complete its operation. Another standard reference type in OpenTracing is FollowsFrom, which means the rootSpan is the ancestor in the DAG, but it does not depend on the completion of the child span, for example if the child represents a best-effort, fire-and-forget cache write.
func formatString(ctx context.Context, helloTo string) string {
span, _ := opentracing.StartSpanFromContext(ctx, "formatString")
defer span.Finish()

helloStr := fmt.Sprintf("Hello, %s!", helloTo)
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()

println(helloStr)
span.LogKV("event", "println")
}

3、 模拟rpc, 跨程序进行追踪。

1
首先启动publisher与formatter,接着执行master

master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package main

import (
"context"
"net/http"
"net/url"
"os"

"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
"github.com/yurishkuro/opentracing-tutorial/go/lib/http"
"github.com/yurishkuro/opentracing-tutorial/go/lib/tracing"
)

func main() {
if len(os.Args) != 2 {
panic("ERROR: Expecting one argument")
}

tracer, closer := tracing.Init("hello-world")
defer closer.Close()
opentracing.SetGlobalTracer(tracer)

helloTo := os.Args[1]

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)
}

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 := xhttp.Do(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:8082/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 := xhttp.Do(req); err != nil {
panic(err.Error())
}
}

formatter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"log"
"net/http"

opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
"github.com/yurishkuro/opentracing-tutorial/go/lib/tracing"
)

func main() {
tracer, closer := tracing.Init("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, %s!", helloTo)
span.LogFields(
otlog.String("event", "string-format"),
otlog.String("value", helloStr),
)
w.Write([]byte(helloStr))
})

log.Fatal(http.ListenAndServe(":8081", nil))
}

publisher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"log"
"net/http"

opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/yurishkuro/opentracing-tutorial/go/lib/tracing"
)

func main() {
tracer, closer := tracing.Init("publisher")
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("publish", ext.RPCServerOption(spanCtx))
defer span.Finish()

helloStr := r.FormValue("helloStr")
println(helloStr)
})

log.Fatal(http.ListenAndServe(":8082", nil))
}

参考资料

jaeger github :github.com/uber/jaeger-client-go
opentracing :github.com/opentracing/opentracing-go
tutorial: github.com/yurishkuro/opentracing-tutorial