参考项目:https://github.com/grpc-ecosystem/grpc-opentracing
之前用函数调用实现了简单jaeger-demo(https://blog.csdn.net/liyunlong41/article/details/87932953),函数之间利用context传递span信息。现在开始在grpc请求中实现简单的grpc-jaeger-demo,span的传递渠道也是利用context。
但是也稍有不同,我们之前是用StartSpanFromContext来模拟从context中启动一个子span,但是StartSpanFromContext或者SpanFromContext只能在同一个服务内使用,grpc中client的context和server的context并不是同一个context,无法使用这两个函数。(参考https://github.com/grpc/grpc-go/issues/130)
如果想通过grpc的context传递span的信息,就需要使用grcp的metadata来传递(一个简单的例子:https://medium.com/@harlow/grpc-context-for-client-server-metadata-91cec8729424)。
同时grpc-client端提供了Inject函数,可以将span的context信息注入到carrier中,再将carrier写入到metadata中,即可完成span信息的传递。
grpc提供了拦截器,我们可以在dial函数里设置拦截器,这样每次请求都会经过拦截器,我们不需要在每个接口中去编写重复的代码。
client端示例代码:
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
82func main() { //init jaeger tracer, closer, err := initJaeger("client", jaegerAgentHost) if err != nil { log.Fatal(err) } defer closer.Close() //dial conn, err := grpc.Dial(addr, grpc.WithInsecure(), clientDialOption(tracer)) if err != nil { log.Fatalf("dial fail, %+vn", err) } //发送请求 req := &delayqueue.PingRequest{Msg:"ping~"} client := delayqueue.NewDelayQueueClient(conn) r, err := client.Ping(context.Background(), req) fmt.Println(r, err) } func clientDialOption(tracer opentracing.Tracer) grpc.DialOption { return grpc.WithUnaryInterceptor(jaegerGrpcClientInterceptor) } type TextMapWriter struct { metadata.MD } //重写TextMapWriter的Set方法,我们需要将carrier中的数据写入到metadata中,这样grpc才会携带。 func (t TextMapWriter) Set(key, val string) { //key = strings.ToLower(key) t.MD[key] = append(t.MD[key], val) } func jaegerGrpcClientInterceptor (ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) { var parentContext opentracing.SpanContext //先从context中获取原始的span parentSpan := opentracing.SpanFromContext(ctx) if parentSpan != nil { parentContext = parentSpan.Context() } tracer := opentracing.GlobalTracer() span := tracer.StartSpan(method, opentracing.ChildOf(parentContext)) defer span.Finish() //从context中获取metadata。md.(type) == map[string][]string md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.New(nil) } else { //如果对metadata进行修改,那么需要用拷贝的副本进行修改。(FromIncomingContext的注释) md = md.Copy() } //定义一个carrier,下面的Inject注入数据需要用到。carrier.(type) == map[string]string //carrier := opentracing.TextMapCarrier{} carrier := TextMapWriter{md} //将span的context信息注入到carrier中 e := tracer.Inject(span.Context(), opentracing.TextMap, carrier) if e != nil { fmt.Println("tracer Inject err,", e) } //创建一个新的context,把metadata附带上 ctx = metadata.NewOutgoingContext(ctx, md) return invoker(ctx, method, req, reply, cc, opts...) } func initJaeger(service string, jaegerAgentHost string) (tracer opentracing.Tracer, closer io.Closer, err error) { cfg := &config.Configuration{ Sampler: &config.SamplerConfig{ Type: "const", Param: 1, }, Reporter: &config.ReporterConfig{ LogSpans: true, LocalAgentHostPort:jaegerAgentHost, }, } tracer, closer, err = cfg.New(service, config.Logger(jaeger.StdLogger)) opentracing.SetGlobalTracer(tracer) return tracer, closer, err }
在grpc-server端,我们使用Extract函数将carrier从metadata中提取出来,这样client端与server端就能建立span信息的关联。我们在server端同样只是修改拦截器,在grpc.NewServer时将我们的拦截器传进去。
server端代码:
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
43func serverOption(tracer opentracing.Tracer) grpc.ServerOption { return grpc.UnaryInterceptor(jaegerGrpcServerInterceptor) } type TextMapReader struct { metadata.MD } //读取metadata中的span信息 func (t TextMapReader) ForeachKey(handler func(key, val string) error) error { //不能是指针 for key, val := range t.MD { for _, v := range val { if err := handler(key, v); err != nil { return err } } } return nil } func jaegerGrpcServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { //从context中获取metadata。md.(type) == map[string][]string md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.New(nil) } else { //如果对metadata进行修改,那么需要用拷贝的副本进行修改。(FromIncomingContext的注释) md = md.Copy() } carrier := TextMapReader{md} tracer := opentracing.GlobalTracer() spanContext, e := tracer.Extract(opentracing.TextMap, carrier) if e != nil { fmt.Println("Extract err:", e) } span := tracer.StartSpan(info.FullMethod, opentracing.ChildOf(spanContext)) defer span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) return handler(ctx, req) }
我们可以在span finish之前利用SetTag添加一些额外的信息,例如request和reply,以及error信息,但是这些信息是不会在client和server中传递的,我们可以在UI中每个span中显示出他们的tag。
WebUI:
下面就是webUI中效果图了,简单展示一下:
最后
以上就是完美嚓茶最近收集整理的关于opentracing: jaeger在grpc中的简单实现的全部内容,更多相关opentracing:内容请搜索靠谱客的其他文章。
发表评论 取消回复