目录

  • 一、链路追踪
  • 1 - 链路追踪简介
  • 2 - 链路追踪技术选型
  • 3 - jaeger安装
  • 4 - jaeger组成
  • 5 - opentracing解析
  • 二、go使用jaeger
  • 1 - 发送单span
  • 2 - 发送嵌套span
  • 3 - grpc发送span
  • 三、goods_web集成jaeger
  • 1 - 添加nacos配置
  • 2 - 添加中间件
  • 3 - 初始化连接注入Tracer
  • 4 - api中调用grpc带上ctx
  • 5 - 优化otgrpc的源码
  • 四、order_web集成jaeger
  • 1 - 复制tracing和otgrpc到order_web中
  • 2 - router修改
  • 3 - grpc调用接口修改
  • 4 - 初始化连接注入Tracer
  • 5 - 添加nacos配置
  • 五、同步srv层和web层的jaeger
  • 1 - otgrpc原理分析
  • 2 - 初始化jaeger
  • 3 - 增加追踪的span
  • 4 - 过滤掉健康检查的jaeger
  • 六、完整源码


一、链路追踪

1 - 链路追踪简介

  • 什么是链路追踪:分布式链路追踪就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等
  • 链路追踪主要功能
  • 故障快速定位:可以通过调用链结合业务日志快速定位错误信息
  • 链路性能可视化:各个阶段链路耗时、服务依赖关系可以通过可视化界面展现出来
  • 链路分析:通过分析链路耗时、服务依赖关系可以得到用户的行为路径,汇总分析应用在很多业务场景

2 - 链路追踪技术选型

Jaeger 关闭 链路跟踪 jaeger链路追踪_golang

3 - jaeger安装

4 - jaeger组成

分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点(我们来实现)、数据存储、查询展示

  • Jaeger组成
  • Jaeger Client:为不同语言实现了符合OpenTracing标准的SDK.应用程序通过API写入数据,client library把trace信息按照应用程序指定的采样策略传递给jaeger-agent
  • Agent:它是一个监听再UDP端口上接收span数据的网络守护进程,它会将数据批量发送给collector.它被设计成一个基础组件,部署到所有的宿主机上.Agent将client library和collector解耦,为client library屏蔽了路由和发现collector的细节
  • collector:接收jaeger-agent发送来的数据.然后将数据写入到后端存储.collector被设计成无状态的组件,因此可以同事运行任意数量的jaeger-collector
  • Data Store:后端存储被设计成一个可插拔的组件,支持将数据写入cassandra,elasticsearch
  • Query:接收查询请求,然后从后端存储系统中检索trace并通过UI进行展示.Query是无状态的,可以启动多个实例,把他们部署再nginx这样的负载均衡器后面

Jaeger 关闭 链路跟踪 jaeger链路追踪_docker_02

5 - opentracing解析

  • 以下内容摘自https://cloud.tencent.com/developer/article/1846107
  • 综述:这是正式的OpenTracing语义标准。OpenTracing是一个跨编程语言的标准,此文档会避免具有语言特性的概念。比如,我们在文档中使用"interface",因为所有的语言都包含"interface"这种概念
  • 版本命名策略:OpenTracing标准使用Major.Minor版本命名策略(即:大版本.小版本),但不包含.Patch版本(即:补丁版本)。如果标准做出不向前兼容的改变,则使用“主版本”号提升。如果是向前兼容的改进,则进行小版本号提升,例如加入新的标准tag, log和SpanContext引用类型
  • OpenTracing数据模型
  • OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References
  • 译者注: Span,可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span
  • OpenTracing API:OpenTracing标准中有三个重要的相互关联的类型,分别是Tracer, Span 和 SpanContext。下面,我们分别描述每种类型的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法
  • Tracer:Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递
  • Span:每个Span包含以下的状态
  • An operation name,操作名称
  • A start timestamp,起始时间
  • A finish timestamp,结束时间
  • Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型
  • Span Log,一组span的日志集合。 每次log操作包含一个键值对,以及一个时间戳。 键值对中,键必须为string,值可以是任意类型。 但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型
  • SpanContext,Span上下文对象
  • References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)
  • SpanContext:每一个SpanContext包含以下状态
  • 任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输
  • Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输
  • Span间关系:一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)

二、go使用jaeger

1 - 发送单span

package main

import (
	"time"

	"github.com/opentracing/opentracing-go"

	"github.com/uber/jaeger-client-go"

	jaegercfg "github.com/uber/jaeger-client-go/config"
)

func main() {
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:           true,
			LocalAgentHostPort: "192.168.78.131:6831",
		},
		ServiceName: "mxshop",
	}

	tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		panic(err)
	}
	opentracing.SetGlobalTracer(tracer)
	defer closer.Close()
	span := opentracing.StartSpan("go-grpc-web")
	time.Sleep(time.Second)
	defer span.Finish()
}
  • 执行完成在jaeger查看

Jaeger 关闭 链路跟踪 jaeger链路追踪_Jaeger 关闭 链路跟踪_03


Jaeger 关闭 链路跟踪 jaeger链路追踪_docker_04

2 - 发送嵌套span

  • 直接新建2个span
opentracing.SetGlobalTracer(tracer)
	defer closer.Close()

	spanA := opentracing.StartSpan("funcA")
	time.Sleep(time.Millisecond * 500)
	defer spanA.Finish()

	spanB := opentracing.StartSpan("funcB")
	time.Sleep(time.Millisecond * 1000)
	defer spanB.Finish()

Jaeger 关闭 链路跟踪 jaeger链路追踪_Jaeger 关闭 链路跟踪_05

  • 删除defer
opentracing.SetGlobalTracer(tracer)
	defer closer.Close()

	spanA := opentracing.StartSpan("funcA")
	time.Sleep(time.Millisecond * 500)
	spanA.Finish()

	spanB := opentracing.StartSpan("funcB")
	time.Sleep(time.Millisecond * 1000)
	spanB.Finish()

Jaeger 关闭 链路跟踪 jaeger链路追踪_golang_06


Jaeger 关闭 链路跟踪 jaeger链路追踪_服务器_07

  • 同tracer的span
opentracing.SetGlobalTracer(tracer)
	defer closer.Close()

	parentSpan := tracer.StartSpan("main")

	spanA := opentracing.StartSpan("funcAAA", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 500)
	spanA.Finish()

	spanB := opentracing.StartSpan("funcBBB", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 1000)
	spanB.Finish()

	parentSpan.Finish()

Jaeger 关闭 链路跟踪 jaeger链路追踪_微服务_08


Jaeger 关闭 链路跟踪 jaeger链路追踪_Jaeger 关闭 链路跟踪_09

  • 在两个span中间添加业务逻辑
parentSpan := tracer.StartSpan("main")

	spanA := opentracing.StartSpan("funcAAA", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 500)
	spanA.Finish()

	time.Sleep(time.Millisecond * 1000)

	spanB := opentracing.StartSpan("funcBBB", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 1000)
	spanB.Finish()

	parentSpan.Finish()

Jaeger 关闭 链路跟踪 jaeger链路追踪_服务器_10

  • 多级嵌套:main -> spanA -> spanB
parentSpan := tracer.StartSpan("main")

	spanA := opentracing.StartSpan("funcAAA", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 500)
	spanA.Finish()

	time.Sleep(time.Millisecond * 1000)

	spanB := opentracing.StartSpan("funcBBB", opentracing.ChildOf(spanA.Context()))
	time.Sleep(time.Millisecond * 1000)
	spanB.Finish()

	parentSpan.Finish()

Jaeger 关闭 链路跟踪 jaeger链路追踪_docker_11

3 - grpc发送span

  • 第三方grpc-opentracing:https://github.com/grpc-ecosystem/grpc-opentracing;直接clone下来使用
  • server/server.go
package main

import (
	"context"
	"net"

	"google.golang.org/grpc"

	"my_test/proto"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
	error) {
	return &proto.HelloReply{
		Message: "hello " + request.Name,
	}, nil
}

func main() {
	g := grpc.NewServer()
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		panic("failed to listen:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("failed to start grpc:" + err.Error())
	}
}
  • client/client.go
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc/credentials/insecure"

	"my_test/otgrpc"

	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"google.golang.org/grpc"

	"my_test/proto"
)

func main() {
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:           true,
			LocalAgentHostPort: "192.168.78.131:6831",
		},
		ServiceName: "mxshop",
	}

	tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		panic(err)
	}
	opentracing.SetGlobalTracer(tracer)
	defer closer.Close()

	conn, err := grpc.Dial("127.0.0.1:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())))
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)
	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

Jaeger 关闭 链路跟踪 jaeger链路追踪_微服务_12


Jaeger 关闭 链路跟踪 jaeger链路追踪_golang_13


三、goods_web集成jaeger

1 - 添加nacos配置

  • goods_web/config/config.go
type JaegerConfig struct {
	Host string `mapstructure:"host" json:"host"`
	Port int    `mapstructure:"port" json:"port"`
	Name string `mapstructure:"name" json:"name"`
}

type ServerConfig struct {
	Name         string         `mapstructure:"name" json:"name"`
	Host         string         `mapstructure:"host" json:"host"`
	Tags         []string       `mapstructure:"tags" json:"tags"`
	Port         int            `mapstructure:"port" json:"port"`
	GoodsSrvInfo GoodsSrvConfig `mapstructure:"goods_srv" json:"goods_srv"`
	JWTInfo      JWTConfig      `mapstructure:"jwt" json:"jwt"`
	ConsulInfo   ConsulConfig   `mapstructure:"consul" json:"consul"`
	JaegerInfo   JaegerConfig   `mapstructure:"jaeger" json:"jaeger"`
}
  • nacos配置
{
  "host": "192.168.124.9",
  "name": "goods_web",
  "port": 8082,
  "tags": ["mxshop","imooc","bobby","goods","web"],
  "goods_srv": {
    "name": "goods_srv"
  },
  "jwt": {
    "key": "VYLDYq3&hGWjWqF$K1ih"
  },
  "consul": {
    "host": "192.168.124.51",
    "port": 8500
  },
  "jaeger": {
    "host": "192.168.124.51",
    "port": 6381,
    "name": "ja_web_api"
  }
}

2 - 添加中间件

  • goods_web/middlewares/tracing.go
package middlewares

import (
	"fmt"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	"web_api/goods_web/global"

	"github.com/gin-gonic/gin"
	jaegercfg "github.com/uber/jaeger-client-go/config"
)

func Trace() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		cfg := jaegercfg.Configuration{
			Sampler: &jaegercfg.SamplerConfig{
				Type:  jaeger.SamplerTypeConst,
				Param: 1,
			},
			Reporter: &jaegercfg.ReporterConfig{
				LogSpans:           true,
				LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.JaegerInfo.Host, global.ServerConfig.JaegerInfo.Port),
			},
			ServiceName: global.ServerConfig.JaegerInfo.Name,
		}

		tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
		if err != nil {
			panic(err)
		}
		opentracing.SetGlobalTracer(tracer)
		defer closer.Close()

		startSpan := tracer.StartSpan(ctx.Request.URL.Path)
		defer startSpan.Finish()

		ctx.Set("tracer", tracer)
		ctx.Set("parentSpan", startSpan)
		ctx.Next()
	}
}
  • goods_web/router/router_goods.go:router调用中间件
package router

import (
	"github.com/gin-gonic/gin"
	"web_api/goods_web/api/goods"
	"web_api/goods_web/middlewares"
)

func InitGoodsRouter(Router *gin.RouterGroup) {
	GoodsRouter := Router.Group("goods").Use(middlewares.Trace())
	{
		GoodsRouter.GET("", goods.List) //商品列表
		//GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New) //该接口需要管理员权限
		GoodsRouter.POST("", goods.New)       // 我们测试先将这2个接口权限关闭掉
		GoodsRouter.GET("/:id", goods.Detail) //获取商品的详情
		//GoodsRouter.DELETE("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Delete) //删除商品
		GoodsRouter.DELETE("/:id", goods.Delete)     // 我们测试先将这2个接口权限关闭掉
		GoodsRouter.GET("/:id/stocks", goods.Stocks) //获取商品的库存
		//GoodsRouter.PUT("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Update)
		GoodsRouter.PUT("/:id", goods.Update)
		//GoodsRouter.PATCH("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.UpdateStatus)
		GoodsRouter.PATCH("/:id", goods.UpdateStatus)
	}
}

3 - 初始化连接注入Tracer

  • goods_web/initialize/init_srv_conn.go
package initialize

import (
	"fmt"
	_ "github.com/mbobakov/grpc-consul-resolver" // It's important
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"web_api/goods_web/global"
	"web_api/goods_web/proto"
	"web_api/goods_web/utils/otgrpc"
)

func InitSrvConn() {
	consulInfo := global.ServerConfig.ConsulInfo
	userConn, err := grpc.Dial(
		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
	)
	if err != nil {
		zap.S().Fatal("[InitSrvConn] 连接 【商品服务失败】")
	}

	global.GoodsSrvClient = proto.NewGoodsClient(userConn)
}

4 - api中调用grpc带上ctx

  • goods_web/api/goods/api_goods.go
func List(ctx *gin.Context) {
	//省略。。。
	request.Brand = int32(brandIdInt)

	请求商品的service服务
	r, err := global.GoodsSrvClient.GoodsList(context.WithValue(context.Background(), "ginContext", ctx), request)
	if err != nil {
		zap.S().Errorw("[List] 查询 【商品列表】失败")
		api.HandleGrpcErrorToHttp(err, ctx)
		return
	}
	//省略。。。
}

Jaeger 关闭 链路跟踪 jaeger链路追踪_docker_14

5 - 优化otgrpc的源码

  • goods_web/utils/otgrpc/client.go:源码优化 -> 从ginContext中拿到tracer和parentSpan
func OpenTracingClientInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.UnaryClientInterceptor {
	otgrpcOpts := newOptions()
	otgrpcOpts.apply(optFuncs...)
	return func(
		ctx context.Context,
		method string,
		req, resp interface{},
		cc *grpc.ClientConn,
		invoker grpc.UnaryInvoker,
		opts ...grpc.CallOption,
	) error {
		var err error
		var parentCtx opentracing.SpanContext

		if parent := opentracing.SpanFromContext(ctx); parent != nil {
			parentCtx = parent.Context()
		}

		ginContext := ctx.Value("ginContext")
		switch ginContext.(type) {
		case *gin.Context:
			if itracer, ok := ginContext.(*gin.Context).Get("tracer"); ok {
				tracer = itracer.(opentracing.Tracer)
			}
			if parentSpan, ok := ginContext.(*gin.Context).Get("parentSpan"); ok {
				parentCtx = parentSpan.(*jaegerClient.Span).Context()
			}
		}
	//省略。。。

Jaeger 关闭 链路跟踪 jaeger链路追踪_微服务_15


四、order_web集成jaeger

1 - 复制tracing和otgrpc到order_web中

2 - router修改

  • order_web/router/router_order.go
package router

import (
	"github.com/gin-gonic/gin"
	"web_api/order_web/api/order"
	"web_api/order_web/api/pay"
	"web_api/order_web/middlewares"
)

func InitOrderRouter(Router *gin.RouterGroup) {
	OrderRouter := Router.Group("orders").Use(middlewares.SetUserId()).Use(middlewares.Trace())
	{
		OrderRouter.GET("", order.List) // 订单列表
		//BannerRouter.GET("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), order.List) // 订单列表
		OrderRouter.POST("", order.New)        // 新建订单
		OrderRouter.GET("/:id/", order.Detail) // 订单详情
	}
	PayRouter := Router.Group("pay")
	{
		PayRouter.POST("alipay/notify", pay.Notify)
	}
}

3 - grpc调用接口修改

  • order_web/api/order/api_order.go
func New(ctx *gin.Context) {
	orderForm := forms.CreateOrderForm{}
	if err := ctx.ShouldBindJSON(&orderForm); err != nil {
		api.HandleValidatorError(ctx, err)
	}
	userId, _ := ctx.Get("userId")
	rsp, err := global.OrderSrvClient.CreateOrder(context.WithValue(context.Background(), "ginContext", ctx), &proto.OrderRequest{
		UserId:  int32(userId.(uint)),
		Name:    orderForm.Name,
		Mobile:  orderForm.Mobile,
		Address: orderForm.Address,
		Post:    orderForm.Post,
	})
	//省略

4 - 初始化连接注入Tracer

  • order_web/initialize/init_srv_conn.go
package initialize

import (
	"fmt"
	_ "github.com/mbobakov/grpc-consul-resolver" // It's important
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"web_api/order_web/global"
	"web_api/order_web/proto"
	"web_api/order_web/utils/otgrpc"
)

func InitSrvConn() {
	consulInfo := global.ServerConfig.ConsulInfo
	goodsConn, err := grpc.Dial(
		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
	)
	if err != nil {
		zap.S().Fatal("[InitSrvConn] 连接 【商品服务失败】")
	}

	global.GoodsSrvClient = proto.NewGoodsClient(goodsConn)

	orderConn, err := grpc.Dial(
		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.OrderSrvInfo.Name),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
	)
	if err != nil {
		zap.S().Fatal("[InitSrvConn] 连接 【订单服务失败】")
	}

	global.OrderSrvClient = proto.NewOrderClient(orderConn)

	inventoryConn, err := grpc.Dial(
		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.InventorySrvInfo.Name),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
	)
	if err != nil {
		zap.S().Fatal("[InitSrvConn] 连接 【库存服务失败】")
	}

	global.InventorySrvClient = proto.NewInventoryClient(inventoryConn)
}

5 - 添加nacos配置

  • order_web/config/config.go
type JaegerConfig struct {
	Host string `mapstructure:"host" json:"host"`
	Port int    `mapstructure:"port" json:"port"`
	Name string `mapstructure:"name" json:"name"`
}

type ServerConfig struct {
	Name             string       `mapstructure:"name" json:"name"`
	Host             string       `mapstructure:"host" json:"host"`
	Tags             []string     `mapstructure:"tags" json:"tags"`
	Port             int          `mapstructure:"port" json:"port"`
	GoodsSrvInfo     SrvConfig    `mapstructure:"goods_srv" json:"goods_srv"`
	OrderSrvInfo     SrvConfig    `mapstructure:"order_srv" json:"order_srv"`
	InventorySrvInfo SrvConfig    `mapstructure:"inventory_srv" json:"inventory_srv"`
	JWTInfo          JWTConfig    `mapstructure:"jwt" json:"jwt"`
	ConsulInfo       ConsulConfig `mapstructure:"consul" json:"consul"`
	AliPayInfo       AlipayConfig `mapstructure:"alipay" json:"alipay"`
	JaegerInfo       JaegerConfig `mapstructure:"consul" json:"jaeger"`
}
  • nacos配置
{
  "host": "192.168.124.9",
  "name": "order_web",
  "port": 8083,
  "tags": ["mxshop","imooc","bobby","order","web"],
  "goods_srv": {
    "name": "goods_srv"
  },
  "order_srv": {
    "name": "order_srv"
  },
  "inventory_srv": {
    "name": "inventory_srv"
  },
  "jwt": {
    "key": "VYLDYq3&hGWjWqF$K1ih"
  },
  "consul": {
    "host": "192.168.124.51",
    "port": 8500
  },
  "jaeger": {
    "host": "192.168.124.51",
    "port": 6381,
    "name": "ja_order_api"
  },
  "alipay":{
    "app_id":"2021000121645456",
    "private_key":"MIIEowIBAAKCA",
    "ali_public_key":"MIIBIjANBgkq",
    "notify_url":"http://xxx",
    "return_url":"http://127.0.0.1:8089"
  }
}

五、同步srv层和web层的jaeger

1 - otgrpc原理分析

  • order_web/utils/otgrpc/client.go

2 - 初始化jaeger

  • order_srv/main.go:主要修改grpc.NewServer
package main

import (
	"flag"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/opentracing/opentracing-go"
	"nd/order_srv/handler"
	"nd/order_srv/utils/otgrpc"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/satori/go.uuid"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"

	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"

	"nd/order_srv/global"
	"nd/order_srv/initialize"
	"nd/order_srv/proto"
	"nd/order_srv/utils"
	"nd/order_srv/utils/register/consul"
)

func main() {
	IP := flag.String("ip", "0.0.0.0", "ip地址")
	Port := flag.Int("port", 50060, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0

	//初始化
	initialize.InitLogger()
	initialize.InitConfig()
	initialize.InitDB()
	initialize.InitSrvConn()
	zap.S().Info(global.ServerConfig)

	flag.Parse()
	zap.S().Info("ip: ", *IP)
	if *Port == 0 {
		*Port, _ = utils.GetFreePort()
	}
	zap.S().Info("port: ", *Port)

	//初始化jaeger
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:           true,
			LocalAgentHostPort: "192.168.124.51:6831",
		},
		ServiceName: "mxshop",
	}

	tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		panic(err)
	}
	opentracing.SetGlobalTracer(tracer)
	server := grpc.NewServer(grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer)))

	proto.RegisterOrderServer(server, &handler.OrderServer{})
	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
	if err != nil {
		panic("failed to listen:" + err.Error())
	}

	//注册服务健康检查
	grpc_health_v1.RegisterHealthServer(server, health.NewServer())

	//服务注册
	register_client := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)
	serviceId := fmt.Sprintf("%s", uuid.NewV4())
	err = register_client.Register(global.ServerConfig.Host, *Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId)
	if err != nil {
		zap.S().Panic("服务注册失败:", err.Error())
	}
	zap.S().Debugf("启动服务器, 端口: %d", *Port)

	go func() {
		err = server.Serve(lis)
		if err != nil {
			panic("failed to start grpc:" + err.Error())
		}
	}()

	//监听订单超时topic
	c, _ := rocketmq.NewPushConsumer(
		consumer.WithNameServer([]string{"192.168.124.51:9876"}),
		consumer.WithGroupName("mxshop-order"),
	)

	if err := c.Subscribe("order_timeout", consumer.MessageSelector{}, handler.OrderTimeout); err != nil {
		fmt.Println("读取消息失败")
	}
	_ = c.Start()
	//不能让主goroutine退出

	//接收终止信号
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	_ = c.Shutdown()
	_ = closer.Close()
	if err = register_client.DeRegister(serviceId); err != nil {
		zap.S().Info("注销失败:", err.Error())
	} else {
		zap.S().Info("注销成功:")
	}
}

3 - 增加追踪的span

  • order_srv/handler/handle_order.go:增加追踪的span
  • CreateOrder中初始化context
func (*OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	/*
		新建订单
			1. 从购物车中获取到选中的商品
			2. 商品的价格自己查询 - 访问商品服务 (跨微服务)
			3. 库存的扣减 - 访问库存服务 (跨微服务)
			4. 订单的基本信息表 - 订单的商品信息表
			5. 从购物车中删除已购买的记录
	*/
	orderListener := OrderListener{Ctx: ctx}
	p, err := rocketmq.NewTransactionProducer(
		&orderListener,
		producer.WithNameServer([]string{"192.168.124.51:9876"}),
	)
	//省略。。。
  • 在需要追踪的业务地方增加span
  • 进入后从context先获取父span:parentSpan := opentracing.SpanFromContext(o.Ctx)
  • 追踪开始:shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcart", opentracing.ChildOf(parentSpan.Context()))
  • 追踪结束:shopCartSpan.Finish()
func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	parentSpan := opentracing.SpanFromContext(o.Ctx)

	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	var goodsIds []int32
	var shopCarts []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32)

	shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcart", opentracing.ChildOf(parentSpan.Context()))
	if result := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Find(&shopCarts); result.RowsAffected == 0 {
		o.Code = codes.InvalidArgument
		o.Detail = "没有选中结算的商品"
		return primitive.RollbackMessageState
	}
	shopCartSpan.Finish()

4 - 过滤掉健康检查的jaeger

  • order_srv/utils/otgrpc/server.go
func OpenTracingServerInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.UnaryServerInterceptor {
	otgrpcOpts := newOptions()
	otgrpcOpts.apply(optFuncs...)
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (resp interface{}, err error) {
		if info.FullMethod != "/grpc.health.v1.Health/Check" {
			spanContext, err := extractSpanContext(ctx, tracer)
	//省略