• GO微服务实战第三十一节 案例:如何在微服务中集成 Zipkin 组件?


    这一课时我们就来进行案例实战,选择当前流行的链路追踪组件 Zipkin 作为示例,演示如何在 Go 微服务中集成 Zipkin。对于很多使用了 Go 微服务框架的用户来说,其框架本身就拥有 Trace 模块,如 Go-kit。所以本课时我们就在 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 支持 OpenTracing API,并使用 opentracing-go 包为其服务器和客户端提供追踪中间件。Zipkin、LightStep 和 AppDash 是已支持的追踪组件,通过 OpenTracing API 与 Go-kit 一起使用。

    应用架构图

    本课时将会介绍如何在 Go-kit 中集成 Zipkin 进行链路调用的追踪,包括HTTP 和 gRPC 两种调用方式。在具体介绍这两种调用方式之前,我们先来看一下 Go-kit 集成 Zipkin 的应用架构,如下图所示:

    Drawing 0.png

    Go-kit 集成 Zipkin 的应用架构图

    从架构图中可以看到:我们构建了一个服务网关,通过 API 网关调用具体的微服务,所有的服务都注册到 Consul 上;当客户端的请求到来之时,网关作为服务端的门户,会根据配置的规则,从 Consul 中获取对应服务的信息,并将请求反向代理到指定的服务实例。

    涉及的业务服务与组件包含以下 4 个:

    • Consul,本地安装并启动;

    • Zipkin,本地安装并启动;

    • API Gateway,微服务网关;

    • String Service,字符串服务,是基于 Kit 构建的,提供基本的字符串操作。

    HTTP 调用方式的链路追踪

    关于 HTTP 调用方式的链路追踪,下面我们将依次构建微服务网关、业务服务,并进行结果验证。

    1. API 网关构建

    在网关(gateway)中增加链路追踪的采集逻辑,同时在反向代理中增加追踪(tracer)设置。

    Go-kit 在 tracing 包中默认添加了 Zipkin 的支持,所以集成工作会比较轻松。在开始之前,需要下载以下依赖:

    # zipkin 官方库
    go get github.com/openzipkin/zipkin-go
    
    • 1
    • 2

    下面三个包都是依赖,按需下载

    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://localhost:9411/api/ v2/spans", "Zipkin server url")
        )
    flag.Parse()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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)
    
    • 1
    • 2

    tags := map[string]string{
    “component”: “gateway_server”,
    }

    handler := zipkinHTTPsvr.NewServerMiddleware(
    zipkinTracer,
    zipkinHTTPsvr.SpanName(“gateway”),
    zipkinHTTPsvr.TagResponseSize(true),
    zipkinHTTPsvr.ServerTags(tags),
    )(proxy)

    网关接收请求后,会创建一个 Span,其中的 traceId 将作为本次请求的唯一编号,网关必须把这个 traceID 传递给字符串服务,字符串服务才能为该请求持续记录追踪信息。在 ReverseProxy 中能够完成这一任务的就是 Transport,我们可以使用 zipkin-go 的 middleware/HTTP 包提供的 NewTransport 替换系统默认的 HTTP.DefaultTransport。代码如下所示:

    // NewReverseProxy 创建反向代理处理方法
    func NewReverseProxy(client *api.Client, zikkinTracer *zipkin.Tracer, logger log.Logger) *HTTPutil.ReverseProxy {
    
    //创建 Director
    director := func(req *HTTP.Request) {
        //省略
    }
    
    // 为反向代理增加追踪逻辑,使用如下 RoundTrip 代替默认 Transport
    roundTrip, _ := zipkinHTTPsvr.NewTransport(zikkinTracer, zipkinHTTPsvr.TransportTrace(true))
    
    return &HTTPutil.ReverseProxy{
        Director:  director,
        Transport: roundTrip,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2

    }

    至此,API 网关服务的搭建就完成了。

    2. 业务服务构建

    创建追踪器与网关的处理方式一样,我们就不再描述。字符串服务对外提供了两个接口:字符串操作(/op/{type}/{a}/{b})和健康检查(/health)。定义如下:

    endpoint := MakeStringEndpoint(svc)
    //添加追踪,设置 span 的名称为 string-endpoint
    endpoint = Kitzipkin.TraceEndpoint(zipkinTracer, "string-endpoint") (endpoint)
    
    • 1
    • 2
    • 3

    //创建健康检查的 Endpoint
    healthEndpoint := MakeHealthCheckEndpoint(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()
    
    zipkinServer := zipkin.HTTPServerTrace(zipkinTracer, zipkin.Name ("HTTP-transport"))
    
    options := []KitHTTP.ServerOption{
        KitHTTP.ServerErrorLogger(logger),
        KitHTTP.ServerErrorEncoder(KitHTTP.DefaultErrorEncoder),
        zipkinServer,
    }
    
    // ...
    
    return r
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3

    }

    至此,所有的代码修改工作已经完成,下一步就是启动测试、对结果验证了。

    3. 结果验证

    我们可以访问 http://localhost:9090/string-service/op/Diff/abc/bcd,查看字符串服务的请求结果,如下图所示:

    Drawing 1.png

    结果验证截图

    可以看到,通过网关,我们可以正常访问字符串服务提供的接口。下面我们通过 Zipkin UI 来查看本次链路调用的信息,如下图所示:

    Drawing 2.png

    Zipkin UI 查看链路调用的信息截图

    在浏览器请求之后,可以在 Zipkin UI 中看到发送的请求记录(单击上方“Try Lens UI”切换成了 Lens UI,效果还不错),点击查看详细的链路调用情况,如下图所示:

    Drawing 3.png

    Lens UI 截图

    从调用链中可以看到,本次请求涉及两个服务:gateway-service 和 string-service。

    整个链路有 3 个 Span:gateway、HTTP-transport 和 string-endpoint,确实如我们所定义的一样。这里我们主要看一下网关中的 Gateway Span 详情,如下图所示:

    Drawing 4.png

    Gateway Span 详情截图

    Gateway 访问字符串服务的时候,其实是作为一个客户端建立连接并发起调用,然后等待 Server 写回响应结果,最后结束客户端的调用。通过上图的展开,我们清楚地了解这次调用(Span)打的标签(tag),包括 method、path 等。

    gRPC 调用方式的链路追踪

    上面我们分析了微服务中 HTTP 调用方式的链路追踪,Go-kit 中的 transport 层可以方便地切换 RPC 调用方式,所以下面我们就来介绍下基于 gRPC 调用方式的链路追踪。本案例的实现是在前面HTTP 调用的代码基础上进行修改,并增加测试的调用客户端。

    1. 定义 protobuf 文件

    我们首先来定义 protobuf 文件及生成对应的 Go 文件。

    syntax = "proto3";
    
    • 1

    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_out=plugins=grpc:.
    
    • 1

    生成的 string.pb.go 可以参见源码,此处不再展开。

    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"))
    
    	handler := NewGRPCServer(ctx, endpts, serverTracer)
    	gRPCServer := grpc.NewServer()
    	pb.RegisterStringServiceServer(gRPCServer, handler)
    	errChan <- gRPCServer.Serve(listener)
    }()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    要增加 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在构造 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"
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    func StringDiff(conn *grpc.ClientConn, clientTracer kitgrpc.ClientOption) service.Service {

    var ep = grpctransport.NewClient(conn,
    	"pb.StringService",
    	"Diff",
    	EncodeGRPCStringRequest, // 请求的编码
    	DecodeGRPCStringResponse, // 响应的解码
    	pb.StringResponse{}, //定义返回的对象
    	clientTracer, //客户端的 GRPCClientTrace
    ).Endpoint()
    
    StringEp := endpts.StringEndpoints{
    	StringEndpoint: ep,
    }
    return StringEp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    }

    从客户端调用的定义可以看到,传入的是 grpc 连接和客户端的 trace 上下文。这里需要注意的是 GRPCClientTrace 的初始化,测试 gRPC 调用的客户端时将会传入该参数。

    4. 测试 gRPC 调用的客户端

    编写 client_test.go,调用我们在前面已经定义的 client.StringDiff 方法,代码如下:

    	//... zipkinTracer 的构造省略
    	tr := zipkinTracer
    	// 设定根 Span 的名称
    	parentSpan := tr.StartSpan("test")
    	defer parentSpan.Flush() // 写入上下文
    
    ctx := zipkin.NewContext(context.Background(), parentSpan)
    //初始化 GRPCClientTrace
    clientTracer := kitzipkin.GRPCClientTrace(tr) 
    conn, err := grpc.Dial(*grpcAddr, grpc.WithInsecure(), grpc.WithTimeout (1*time.Second))
    if err != nil {
    	fmt.Println("gRPC dial err:", err)
    }
    defer conn.Close()
    // 获取 rpc 调用的 endpoint,发起调用
    svr := client.StringDiff(conn, clientTracer)
    result, err := svr.Diff(ctx, "Add", "ppsdd")
    if err != nil {
    	fmt.Println("Diff error", err.Error())
    
    }
    
    fmt.Println("result =", result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5

    客户端在调用之前,我们构建了要传入的 GRPCClientTrace,作为获取 rpc 调用的 endpoint 的参数,设定调用的父 Span 名称,这个上下文信息会传入 Zipkin 服务端。调用输出的结果如下:

    ts=2020-9-24T15:27:06.817056Z caller=client_test.go:51 tracer=Zipkin type=Native URL=http://localhost:9411/api/v2/spans
    result = dd
    
    • 1
    • 2

    测试用例的调用结果正确,我们来看一下 Zipkin 中记录的调用链信息。点击查看详情,可以看到本次请求涉及两个服务:test-service 和 string-service。如图所示:

    Drawing 5.png


    精选评论

  • 相关阅读:
    QML控件类型:ToolTip
    MyBatis 操作数据库
    Mybatis引子—Mybatis简单使用
    MQ基础(RabbitMQ)
    CSS 选择器:精通网页样式的基础
    【牛客-算法】NC61 两数之和(哈希表的运用,C++)
    支持向量机基本原理,Libsvm工具箱详细介绍,基于支持向量机SVM的人脸朝向识别
    Docker网络及CPU资源控制
    文件夹中lib,dll含义
    【setxattr+userfaultfd】SECCON2020-kstack
  • 原文地址:https://blog.csdn.net/fegus/article/details/126499002