grpc中间件之链路追踪(otel+jaeger)

参考文档

https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/examples/client/main.go

https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/examples/server/main.go

https://github.com/open-telemetry/opentelemetry-go/blob/main/example/jaeger/main.go

直接展示代码:

client代码:

go 复制代码
package main

import (
	"context"
	"fmt"
	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
	myInterceptor "go_grpc/grpc_middleware/interceptor"
	"go_grpc/grpc_middleware/model"
	"go_grpc/grpc_middleware/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"log"
	"os"
	"time"
)

func main() {
	//创建traceProvider
	tp, err := myInterceptor.NewTracerProvider("http://localhost:14268/api/traces", "grpc_mid_client")
	if err != nil {
		fmt.Println("NewTracerProvider err:", err)
		os.Exit(1)
	}
	conn, err := grpc.Dial("localhost:4399", grpc.WithTransportCredentials(insecure.NewCredentials()),
		//一元拦截器
		grpc.WithChainUnaryInterceptor(
			//openTelemetry 链路追踪
			otelgrpc.UnaryClientInterceptor(otelgrpc.WithTracerProvider(tp)),
		),
		//流拦截器
		grpc.WithChainStreamInterceptor(func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
			// Pre-processing logic
			s := time.Now()
			cs, err := streamer(ctx, desc, cc, method, opts...)
			// Post processing logic
			log.Printf("method: %s, latency: %s\n", method, time.Now().Sub(s))
			return cs, err
		}),
	)

	//必须执行这一步,才能形成正常的链路追踪
	defer func() {
		println("关闭TracerProvider。所有注册的跨度处理器都会按照它们注册的顺序关闭,并释放所有持有的计算资源。")
		if err := tp.Shutdown(context.Background()); err != nil {
			panic(err)
		}
	}()
	if err != nil {
		log.Fatalf("connection failed,err:%s", err)
	}
	client := pb.NewOrderClient(conn)
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	//客户端发送
	resp, err := client.OrderDetail(ctx, &pb.OrderReq{
		OrderId: 5,
	})
	if err != nil {
		log.Fatalf("orderDetail failed,err:%s", err)
	}
	fmt.Printf("resp:%v\n", resp)

}

server代码(这里只展示核心代码):

go 复制代码
	//创建traceProvider
	tp, err := myInterceptor.NewTracerProvider("http://localhost:14268/api/traces", "grpc_mid_server")
	if err != nil {
		fmt.Println("NewTracerProvider err:", err)
		os.Exit(1)
	}

	orderServer := service.NewOrderService()
	rpcServer := grpc.NewServer(
		//4.引入grpc-middleware定义的拦截器
		grpc.ChainUnaryInterceptor(
			//openTelemetry 链路追踪
			otelgrpc.UnaryServerInterceptor(otelgrpc.WithTracerProvider(tp)),
		),
	)
	pb.RegisterOrderServer(rpcServer, orderServer)

myInterceptor包:

go 复制代码
package interceptor

import (
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
)

// NewTracerProvider 创建trace的提供者
// tracerProvider returns an OpenTelemetry TracerProvider configured to use
// the Jaeger exporter that will send spans to the provided url. The returned
// TracerProvider will also use a Resource configured with all the information
// about the application.
// 参考gitHub example :https://github.com/open-telemetry/opentelemetry-go/blob/main/example/jaeger/main.go
// 访问 http://127.0.0.1:16686/ 即可通过jaeger查看调用过程
func NewTracerProvider(url string, service string) (*tracesdk.TracerProvider, error) {
	// Create the Jaeger exporter
	// 创建 Jaeger exporter
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
	if err != nil {
		return nil, err
	}
	tp := tracesdk.NewTracerProvider(
		// Always be sure to batch in production.
		tracesdk.WithBatcher(exp),
		// Record information about this application in a Resource.
		tracesdk.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String(service),
			attribute.String("environment", "dev"),
			attribute.Int64("ID", 1),
		)),
	)
	//SetTracerProvider将"tp"注册为全局跟踪提供程序。
	otel.SetTracerProvider(tp)
	//传播(Propagation)是在服务和进程之间传递上下文的机制。它对上下文对象进行序列化或反序列化,并提供相关的跟踪(Trace)信息,以便从一个服务传播到另一个服务。
	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
	return tp, nil
}

代码写好后,先启动server端的代码,再启动client端的代码。然后登录http://127.0.0.1:16686/ 即可通过jaeger查看调用过程。(要先下载jaeger)

【如何看客户端的trace信息有没有传递到服务端?】

可以在server端打印上下文的入站metadata:

go 复制代码
md, ok := metadata.FromIncomingContext(ctx) //如果客户端那边有开启链路追踪,这里就能输出:traceparent:[00-c92465d487349809e1c1157ba4133f77-0aa3bcd98ed5fedb-01]
fmt.Printf("md:%v\n", md)

可以看到输出metadata携带了trace信息,说明客户端的trace顺利传递到服务端了:

相关推荐
Yeats_Liao1 小时前
Go Web 编程快速入门 · 04 - 请求对象 Request:头、体与查询参数
前端·golang·iphone
poemyang3 小时前
职责分离的艺术:剖析主从Reactor模型如何实现极致的并发性能
rpc·reactor·事件驱动
驰羽4 小时前
[GO]gin框架:ShouldBindJSON与其他常见绑定方法
开发语言·golang·gin
猫梦www8 小时前
力扣21:合并两个有序链表
数据结构·算法·leetcode·链表·golang·力扣
小白要努力sgy12 小时前
待学习--中间件
学习·中间件
std787913 小时前
Rust 与 Go – 比较以及每个如何满足您的需求
开发语言·golang·rust
草莓熊Lotso15 小时前
基于容器适配器模式的 Stack 与 Queue 实现:复用底层容器的优雅设计
c++·网络协议·rpc·适配器模式
007php00715 小时前
猿辅导Java面试真实经历与深度总结(二)
java·开发语言·python·计算机网络·面试·职场和发展·golang
poemyang1 天前
从C10K到Reactor:事件驱动,如何重塑高并发服务器的网络架构
rpc·reactor·事件驱动
Dobby_051 天前
【Go】C++ 转 Go 第(二)天:变量、常量、函数与init函数
vscode·golang·go