「码动四季·开源同行」go实战案例:如何在微服务中集成 Zipkin 组件?

张开发
2026/4/14 2:36:22 15 分钟阅读

分享文章

「码动四季·开源同行」go实战案例:如何在微服务中集成 Zipkin 组件?
本文我们就来进行案例实战选择当前流行的链路追踪组件 Zipkin作为示例演示如何在 Go 微服务中集成 Zipkin。对于很多使用了Go 微服务框架的用户来说其框架本身就拥有Trace 模块如 Gokit。所以本文我们就在 Go-kit 微服务的案例中集成 Zipkin。Zipkin 社区提供了诸如 zipkin-go、zipkin-go-opentracing、go-zipkin 等 Go 客户端库后面我们会介绍如何将其中的 zipkin-go-opentracing组件地址参见 https://github.com/openzipkin-contrib/zipkin-go-opentracing集成到微服务中并加以应用。Go-kit 微服务框架的 tracing 包为服务提供了 Dapper 样式的请求追踪。Go-kit 支持 OpenTracingAPl并使用opentracing-go 包为其服务器和客户端提供追踪中间件。Zipkin、LightStep 和 AppDash是已支持的追踪组件通过OpenTracing APl 与Go-kit一起使用。应用架构图本文将会介绍如何在 Go-kit 中集成 Zipkin进行链路调用的追踪包括HTTP 和gRPC 两种调用方式在具体介绍这两种调用方式之前我们先来看一下 Go-kit 集成 Zipkin 的应用架构如下图所示:从架构图中可以看到我们构建了一个服务网关通过API网关调用具体的微服务所有的服务都注册到Consul上当客户端的请求到来之时网关作为服务端的门户会根据配置的规则从Consul中获取对应服务的信息并将请求反向代理到指定的服务实例。涉及的业务服务与组件包含以下4个Consul本地安装并启动Zipkin本地安装并启动;APlGateWay微服务网关;String Service字符串服务是基于 Kit 构建的提供基本的字符串操作。HTTP调用方式的链路追踪关于HTTP调用方式的链路追踪下面我们将依次构建微服务网关、业务服务并进行结果验证。1.API网关构建在网关gateway中增加链路追踪的采集逻辑同时在反向代理中增加追踪tracer设置。Go-kit 在 tracing 包中默认添加了 Zipkin 的支持所以集成工作会比较轻松。在开始之前需要下载以下依赖:#zipkin官方库 go get github.com/openzipkin/zipkin-go下面三个包都是依赖按需下载git clone https://github.com/googleapis/googleapis.git [your GOPATH]/src/google.golang.org/genproto git clone https://github.com/grpc/grpc-gO.git [your GOPATH]/src/google. golang.org/grpc git clone https://github.com/golang/text.git [your GOPATH]/src/golang. org/text作为链路追踪的第一站和最后一站”网关会将客户端的请求转发给对应的业务服务并将响应的结果返回给客户端。我们需要截获到达网关的所有请求记录追踪信息。在下面这个示例中网关是作为外部请求的服务端同时作为字符串服务的客户端反向代理内部实现其代码实现如下:// 创建环境变量 var ( // consul 环境变量省略 zipkinURL flag.String(zipkin.url, HTTP://1ocalhost:9411/api/ v2/spans, zipkin server url) ) flag .Parse () Var zipkinTracer zipkin.Tracer { var( err error hostPort localhost:9090 serviceName gateway-service useNoopTracer (zipkinURL ) reporter zipkinHTTP.NewReporter(*zipkinURL) )// zipkin 相关的配置变量 defer reporter.Close) zEP, _: zipkin.NewEndpoint(serviceName, hostPort) //构建 zipkinTracer zipkinTracer, err zipkin.NewTracer( reporter, zipkin.WithLocalEndpoint(zEP), zipkin.WithNoopTracer (useNoopTracer), ) if err ! nil { logger.Log(err, err) oS.Exit(1) } if !useNoopTracer { logger.Log(tracer, Zipkin, type, Native, URL, *zipkinURL) } }我们使用的传输方式为 HTTP可以使用 zipkin-go 提供的 middleWare/HTTP 包它采用装饰者模式把我们的 HTTP.Handler 进行封装然后启动 HTTP监听代码如下所示:// 创建反向代理 proxy : NewReverseProxy(consulclient, zipkinTracer, logger) tags : map[string]string{ component:gateway_server, } handler : zipkinHTTPsvr.NewSerVerMiddleWare( zipkinTracer, zipkinHTTPsvr.SpanName(gateWay), zipkinHTTPsvr.TagResponseSize(true), zipkinHTTPsvr.SerVerTags(tags), )(proxy)网关接收请求后会创建一个 Span其中的 traceld将作为本次请求的唯一编号网关必须把这个tracelD传递给字符串服务字符串服务才能为该请求持续记录追踪信息。在 ReverseProxy 中能够完成这一任务的就是Transport我们可以使用 zipkin-go 的 middleware/HTTP 包提供的 NewTransport替换系统默认的 HTTP.DefaultTransport。代码如下所示:// NewReverseProxy 创建反向代理处理方法 func NewReverseProxy(client *api.Client, zikkinTracer *zipkin.Tracer, logger log.Logger) *HTTPutil.ReverseProxy { span classhljs-comment//创建 Director/span span classhljs-attrdirector/span : func(req *HTTP.Request) { span classh1js-comment//省略/span } spanclasshljs-comment//为反向代理增加追踪逻辑使用如下RoundTrip代替默认 Transport/span roundTrip span classh1js-attr_/span : zipkinHTTPsvr.NewTransportzikkinTracer zipkinHTTPsvr.TransportTrace(spanclassh1js- literaltrue/span)) span classh1js-keywordreturn/span amp; HTTPuti1.ReverseProxy{ span classhljs-attrDirector/span director, span classhljs-attrTransport/span: roundTrip, } }至此API网关服务的搭建就完成了。2.业务服务构建创建追踪器与网关的处理方式一样我们就不再描述。字符串服务对外提供了两个接口字符串操作(/op/{type}/{a}/{b}和健康检查/health。定义如下endpoint : MakeStringEndpoint(svc) // 添加追踪设置 span 的名称为 string-endpoint endpoint Kitzipkin.TraceEndpoint(zipkinTracer, string-endpoint) (endpoint) // 创建健康检查的Endpoint healthEndpoint : MakeHealth CheckEndpoint(svc) //添加追踪设置 span 的名称为 health-endpoint healthEndpoint Kitzipkin.TraceEndpoint(zipkinTracer, health-endpoint) (healthEndpoint)Go-kit 提供了对 zipkin-go 的封装上面的实现中直接调用中间件TraceEndpoint 对字符串服务的两个Endpoint进行设置。除了Endpoint还需要追踪 Transport。可以修改 transports.go 的 MakeHTTPHandler 方法增加参数 zipkinTracer然后在 SerVerOption 中设置追踪参数。代码如下// MakeHTTPHandler make HTTP handler use mux func MakeHTTPHandler(ctx context.Context, endpoints ArithmeticEndpoints, zipkinTracer *gozipkin.Tracer logger log.Logger) HTTP.Handler { r : mux.NewRouter() span classhljs-attrzipkinServer/span zipkin.HTTPServerTrace(zipkinTracer, zipkin.Name (span classhljs- stringHTTP-transport/span)) span classhljs-attroptions/span : []kitHTTP.ServerOption{ KitHTTP.ServerErrorLogger(logger), KitHTTP.SerVerErrorEncoder(KitHTTP.DefaultErrorEncoder),zipkinserver, } span classh1js-comment// . . ./span span classhljs-keywordreturn/span r }至此所有的代码修改工作已经完成下一步就是启动测试、对结果验证了。3.结果验证我们可以访问 http://localhost:9090/string-Service/op/Diff/abc/bcd查看字符串服务的请求结果如下图所示:可以看到通过网关我们可以正常访问字符串服务提供的接口。下面我们通过ZipkinU来查看本次路调用的信息如下图所示:在浏览器请求之后可以在 ZipkinU中看到发送的请求记录单击上方Try LensUI切换成了LensUI效果还不错点击查看详细的链路调用情况如下图所示:从调用链中可以看到本次请求涉及两个服务gateway-service 和 string-service。整个链路有 3 个 SpangateWay、HTTP-transport 和 string-endpoint确实如我们所定义的一样。这里我们主要看一下网关中的GateWay Span 详情如下图所示:GateWay访问字符串服务的时候其实是作为一个客户端建立连接并发起调用然后等待 Server写回响应结果最后结束客户端的调用。通过上图的展开我们清楚地了解这次调用Span打的标签(tag包括method、path 等。gRPC调用方式的链路追踪上面我们分析了微服务中HTTP 调用方式的链路追踪Go-kit 中的 transport 层可以方便地切换 RPC调用方式所以下面我们就来介绍下基于gRPC 调用方式的链路追踪。本案例的实现是在前面HTTP 调用的代码基础上进行修改并增加测试的调用客户端。1.定义protobuf文件我们首先来定义 protobuf 文件及生成对应的 Go 文件。syntax proto3; package pb; service StringService{ rpc Diff(StringRequest) returns (StringResponse){} } message StringRequest { string request_type 1; string a 2; string b 3; } message StringResponse { string result 1; string err 2; }这里提供了字符串服务中的 Diff 方法客户端通过 gRPC 调用字符串服务。使用 proto工具生成对应的Go语言文件protoc string-proto --go_outpluginsgrpc:.生成的 string·pb.go 可以参见Verify two-factor authentication此处不再展开。2.定义 gRPC Server在字符串服务中增加gRPC serVer 的实现并织入gRPC 链路追踪的相关代码。// grpc server go func) { fmt.Println(grpc Server start at port *grpcAddr) listener, err : net.Listen(tcp, *grpcAddr) if err ! nil { errChan - err return } serverTracer : kitzipkin.GRPCServerTrace(zipkinTracer, kitzipkin.Name(string-grpc-transport)) span classhljs-attrhandler/span : NewGRPCServer(ctx, endpts, serverTracer) span classhljs-attrgRPCServer/span : grpc.NewServer() pb.RegisterStringServiceServer(gRPCServer handler) errChan lt;- gRPCServer.Serve(listener) }()要增加 Trace 的中间件其实就是在gRPC 的 SerVerOption 中追加 GRPCSerVerTrace。我们增加的通用 Span 名为string-grpc-transport。接下来就是在endpoint 中增加暴露接口的gRPC 实现代码如下:func (se StringEndpoints) Diff(ctx context.Context a b string) (string error) { resp err : se.StringEndpoint(ctx, StringRequest{ RequestType: Diff, A: a, B: b, }) response : resp.(stringResponse) return response.Result, err }在构造 StringRequest 时我们根据调用的 Diff 方法指定了请求参数为Diff下面即可定义 RPC调用的客户端。3.定义服务gRPC调用的客户端字符串服务提供对外的客户端调用定义方法名为 StringDiff返回 StringEndpoint代码如下:import ( grpctransport github.com/go-kit/kit/transport/grpc kitgrpc github.com/go-kit/kit/transport/grpc github.com/longjoy/micro-go-course/section35/zipkin-kit/pb endpts github.com/longjoy/micro-go-course/section35/zipkin-kit/string- service/endpoint github.com/longjoy/micro-go-course/section35/zipkin-kit/string-service/service google.golang.org/grpc ) func StringDiff(conn *grpc.ClientConn, clientTracer kitgrpc.ClientOption) service.Service { span classhljs-keywordvar/span ep grpctransport.Newclient(conn, span classhljs-stringpb.Stringservice/span, span classhljs-stringDiff/span, EncodeGRPCStringRequestspan classhljs-comment// 请求的编码/span DecodeGRPCStringResponsespan classhljs-comment// 响应的解码/span pb.StringResponse{}span classhljs-comment// 定义返回的对象/span clientTracerspan classhljs-comment// 客户端的 GRPcclientTrace/span ) .Endpoint) span classhljs-attrStringEp/span : endpts.StringEndpoints{ span classhljs-attrStringEndpoint/span: ep, } span classhljs-keywordreturn/span StringEp }从客户端调用的定义可以看到传入的是grpc 连接和客户端的 trace上下文。这里需要注意的是GRPCClientTrace 的初始化测试 gRPC 调用的客户端时将会传入该参数。4.测试gRPC调用的客户端编写client_test.go调用我们在前面已经定义的 client.StringDiff 方法代码如下//...zipkinTracer的构造省略 tr : zipkinTracer // 设定根Span的名称 parentSpan : tr.Startspan(test) deferparentSpan.Flush() // 写入上下文 span classhljs-attrctx/span : zipkin.NewContext(context.Background(),parentspan) span classh1js-comment//初始化 GRPcclientTrace/span span classhljs-attrclientTracer/span : kitzipkin.GRPcclientTrace(tr) conn span classh1js-attrerr/span : grpc.Dial(*grpcAddr, grpc.withInsecure() grpc.WithTimeout span classhljs- numberl/span*time.Second)) span classhljs-keywordif/span err ! nil { fmt.Println(span classhljs-stringgRPC dial err:/span err) } defer conn.Close() spanclasshljs-comment//获取rpc 调用的endpoint发起调用/span span classhljs-attrsvr/span : client.StringDiff(conn clientTracer) resultspan classhljs-attrerr/span : svr.Diff(ctx span classhljs- stringAdd/spanspan classh1js-stringppsdd/span) span classhljs-keywordif/span err ! nil { fmt.Println(span classhljs-stringDiff error/span, err.Error()) } fmt.Println(span classhljs-stringresult /span, result)客户端在调用之前我们构建了要传入的 GRPCClientTrace作为获取rpc调用的 endpoint 的参数设定调用的父 Span 名称这个上下文信息会传入Zipkin服务端。调用输出的结果如下ts2020-9-24T15:27:06.817056z ca11erclient_test.go:51 tracerZipkin typeNative URLhttp://1oca1host: 9411/api/v2/spans result dd测试用例的调用结果正确我们来看一下Zipkin中记录的调用链信息。点击查看详情可以看到本次请求涉及两个服务test-service 和 string-service。如图所示

更多文章