zoukankan      html  css  js  c++  java
  • [系列] go-gin-api 路由中间件

    概述

    首先同步下项目概况:

    上篇文章分享了,路由中间件 - Jaeger 链路追踪(理论篇),这篇文章咱们接着分享:路由中间件 - Jaeger 链路追踪(实战篇)。

    这篇文章,确实让大家久等了,主要是里面有一些技术点都是刚刚研究的,没有存货。

    先看下咱们要实现的东西:

    API 调用了 5 个服务,其中 4 个 gRPC 服务,1 个 HTTP 服务,服务与服务之间又相互调用:

    • Speak 服务,又调用了 Listen 服务 和 Sing 服务。
    • Read 服务,又调用了 Listen 服务 和 Sing 服务。
    • Write 服务,又调用了 Listen 服务 和 Sing 服务。

    咱们要实现的就是查看 API 调用的链路。

    关于一些理论的东西,大家可以去看看上篇文章或查阅一些资料,这篇文章就是实现怎么用。

    OK,开整。

    Jaeger 部署

    咱们使用 All in one 的方式,进行本地部署。

    下载地址:https://www.jaegertracing.io/download/

    我的电脑是 macOS 选择 -> Binaries -> macOS

    下载后并解压,会发现以下文件:

    • example-hotrod
    • jaeger-agent
    • jaeger-all-in-one
    • jaeger-collector
    • jaeger-ingester
    • jaeger-query

    进入到解压后的目录执行:

    ./jaeger-all-in-one
    

    目测启动后,访问地址:

    http://127.0.0.1:16686/

    到这,Jaeger 已经部署成功了。

    准备测试服务

    准备的五个测试服务如下:

    听(listen)

    • 端口:9901
    • 通讯:gRPC

    说(speak)

    • 端口:9902
    • 通讯:gRPC

    读(read)

    • 端口:9903
    • 通讯:gRPC

    写(write)

    • 端口:9904
    • 通讯:gRPC

    唱(sing)

    • 端口:9905
    • 通讯:HTTP

    听、说、读、写、唱,想这几个服务的名称就花了好久 ~

    我默认大家都会写 grpc 服务,如果不会写的,可以查看下我原来的文章《Go gRPC Hello World》。

    应用示例

    实例化 Tracer

    func NewJaegerTracer(serviceName string, jaegerHostPort string) (opentracing.Tracer, io.Closer, error) {
    
    	cfg := &jaegerConfig.Configuration {
    		Sampler: &jaegerConfig.SamplerConfig{
    			Type  : "const", //固定采样
    			Param : 1,       //1=全采样、0=不采样
    		},
    
    		Reporter: &jaegerConfig.ReporterConfig{
    			LogSpans           : true,
    			LocalAgentHostPort : jaegerHostPort,
    		},
    
    		ServiceName: serviceName,
    	}
    
    	tracer, closer, err := cfg.NewTracer(jaegerConfig.Logger(jaeger.StdLogger))
    	if err != nil {
    		panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v
    ", err))
    	}
    	opentracing.SetGlobalTracer(tracer)
    	return tracer, closer, err
    }
    

    HTTP 注入

    injectErr := jaeger.Tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
    if injectErr != nil {
    	log.Fatalf("%s: Couldn't inject headers", err)
    }
    

    HTTP 拦截

    spCtx, err := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
    if err != nil {
    	ParentSpan = Tracer.StartSpan(c.Request.URL.Path)
    	defer ParentSpan.Finish()
    } else {
    	ParentSpan = opentracing.StartSpan(
    		c.Request.URL.Path,
    		opentracing.ChildOf(spCtx),
    		opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
    		ext.SpanKindRPCServer,
    	)
    	defer ParentSpan.Finish()
    }
    

    gRPC 注入

    func ClientInterceptor(tracer opentracing.Tracer, spanContext opentracing.SpanContext) grpc.UnaryClientInterceptor {
    	return func(ctx context.Context, method string,
    		req, reply interface{}, cc *grpc.ClientConn,
    		invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    
    		span := opentracing.StartSpan(
    			"call gRPC",
    			opentracing.ChildOf(spanContext),
    			opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
    			ext.SpanKindRPCClient,
    		)
    
    		defer span.Finish()
    
    		md, ok := metadata.FromOutgoingContext(ctx)
    		if !ok {
    			md = metadata.New(nil)
    		} else {
    			md = md.Copy()
    		}
    
    		err := tracer.Inject(span.Context(), opentracing.TextMap, MDReaderWriter{md})
    		if err != nil {
    			span.LogFields(log.String("inject-error", err.Error()))
    		}
    
    		newCtx := metadata.NewOutgoingContext(ctx, md)
    		err = invoker(newCtx, method, req, reply, cc, opts...)
    		if err != nil {
    			span.LogFields(log.String("call-error", err.Error()))
    		}
    		return err
    	}
    }
    

    gRPC 拦截

    func serverInterceptor(tracer opentracing.Tracer) grpc.UnaryServerInterceptor {
    	return func(ctx context.Context,
    		req interface{},
    		info *grpc.UnaryServerInfo,
    		handler grpc.UnaryHandler) (resp interface{}, err error) {
    
    		md, ok := metadata.FromIncomingContext(ctx)
    		if !ok {
    			md = metadata.New(nil)
    		}
    
    		spanContext, err := tracer.Extract(opentracing.TextMap, MDReaderWriter{md})
    		if err != nil && err != opentracing.ErrSpanContextNotFound {
    			grpclog.Errorf("extract from metadata err: %v", err)
    		} else {
    			span := tracer.StartSpan(
    				info.FullMethod,
    				ext.RPCServerOption(spanContext),
    				opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
    				ext.SpanKindRPCServer,
    			)
    			defer span.Finish()
    
    			ParentContext = opentracing.ContextWithSpan(ctx, span)
    		}
    
    		return handler(ParentContext, req)
    	}
    }
    

    上面是一些核心的代码,涉及到的全部代码我都会上传到 github,供下载。

    运行

    启动服务

    // 启动 Listen 服务
    cd listen && go run main.go
    
    // 启动 Speak 服务
    cd speak && go run main.go
    
    // 启动 Read 服务
    cd read && go run main.go
    
    // 启动 Write 服务
    cd write && go run main.go
    
    // 启动 Sing 服务
    cd sing && go run main.go
    
    // 启动 go-gin-api 服务
    cd go-gin-api && go run main.go
    

    访问路由

    http://127.0.0.1:9999/jaeger_test

    效果

    就到这吧。

    API 源码地址

    https://github.com/xinliangnote/go-gin-api

    Service 源码地址

    https://github.com/xinliangnote/go-jaeger-demo

    go-gin-api 系列文章

  • 相关阅读:
    PID控制器开发笔记之五:变积分PID控制器的实现
    也说读书
    PID控制器开发笔记之四:梯形积分PID控制器的实现
    PID控制器开发笔记之三:抗积分饱和PID控制器的实现
    PID控制器开发笔记之二:积分分离PID控制器的实现
    PID控制器开发笔记之一:PID算法原理及基本实现
    Linux学习笔记之时间同步the NTP socket is in use, exiting问题
    Python关键点笔记之使用 pyenv 管理多个 Python 版本依赖环境
    K8S学习笔记之二进制部署Kubernetes v1.13.4 高可用集群
    K8S学习笔记之ETCD启动失败注意事项
  • 原文地址:https://www.cnblogs.com/xinliangcoder/p/11604880.html
Copyright © 2011-2022 走看看