前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言微服务框架 - 9.分布式链路追踪-OpenTracing的初步引入

Go语言微服务框架 - 9.分布式链路追踪-OpenTracing的初步引入

作者头像
junedayday
发布2021-11-10 14:50:44
2.3K0
发布2021-11-10 14:50:44
举报
文章被收录于专栏:Go编程点滴

我们从API层到数据库层的链路已经打通,简单的CRUD功能已经可以快速实现。

随着模块的增加,我们会越发感受到系统的复杂性,开始关注系统的可维护性。这时,有个名词会进入我们的视野:分布式链路追踪。相关的内容可以参考这我的两篇文章:

  • OpenTelemetry https://junedayday.github.io/2021/10/14/readings/go-digest-2/
  • Jaeger https://junedayday.github.io/2021/10/20/readings/go-digest-3/

我们接下来直接进入实战。

v0.6.0:分布式链路追踪-OpenTracing的初步引入

项目链接 https://github.com/Junedayday/micro_web_service/tree/v0.6.0

目标

在项目中引入Jaeger为代表的OpenTracing,用一个traceid串联整个请求的链路。

关键技术点

  1. trace的初始化
  2. 将opentracing的设置到grpc和grpc-gateway中
  3. 将traceid引入到log组件中
  4. HTTP请求返回traceid

前两点我将一笔带过,在 https://junedayday.github.io/2021/10/20/readings/go-digest-3/ 这篇中已有详细的讲解

目录构造

代码语言:javascript
复制
--- micro_web_service            项目目录
 |-- gen                            从idl文件夹中生成的文件,不可手动修改
    |-- idl                             对应idl文件夹
       |-- demo                             对应idl/demo服务,包括基础结构、HTTP接口、gRPC接口
      |-- order                            对应idl/order服务,同上
 |-- idl                            原始的idl定义
    |-- demo                            业务package定义,protobuffer的原始定义
    |-- order                           业务order定义,同时干
 |-- internal                       项目的内部代码,不对外暴露
    |-- config                          配置相关的文件夹,viper的相关加载逻辑
    |-- dao                             Data Access Object层,是model层的实现
    |-- gormer                          从pkg/gormer中生成的相关代码,不允许更改
    |-- model                           model层,定义对象的接口方法,具体实现在dao层
    |-- mysql                           MySQL连接
    |-- server                          服务器的实现,对idl中定义服务的具体实现
    |-- service                         service层,作为领域实现的核心部分
     |-- zlog                            封装zap日志的代码实现
  |-- pkg                            开放给第三方的工具库
     |-- gormer                          gormer二进制工具,用于生成Gorm相关Dao层代码
 |-- buf.gen.yaml                   buf生成代码的定义,从v1beta升到v1
 |-- buf.yaml                       buf工具安装所需的工具,从v1beta升到v1
 |-- gen.sh                         生成代码的脚本:buf+gormer
 |-- go.mod                         Go Module文件
 |-- gormer.yaml                    将gormer中的参数移动到这里
 |-- main.go                        项目启动的main函数

1.trace的初始化

创建了一个jaeger的trace并设置到opentracing包里的全局变量中。

代码语言:javascript
复制
traceCfg := &jaegerconfig.Configuration{
  ServiceName: "MyService",
  Sampler: &jaegerconfig.SamplerConfig{
    Type:  jaeger.SamplerTypeConst,
    Param: 1,
  },
  Reporter: &jaegerconfig.ReporterConfig{
    LocalAgentHostPort: "127.0.0.1:6831",
    LogSpans:           true,
  },
}
tracer, closer, err := traceCfg.NewTracer(jaegerconfig.Logger(jaeger.StdLogger))
if err != nil {
  panic(err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)

2.将opentracing的设置到grpc和grpc-gateway中

利用了拦截器的特性,类似于middleware。

代码语言:javascript
复制
// grpc-gateway
opts := []grpc.DialOption{
  grpc.WithInsecure(),
  grpc.WithUnaryInterceptor(
    grpc_opentracing.UnaryClientInterceptor(
      grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
    ),
  ),
}

if err := demo.RegisterDemoServiceHandlerFromEndpoint(ctx, mux, fmt.Sprintf(":%d", config.Viper.GetInt("server.grpc.port")), opts); err != nil {
  return errors.Wrap(err, "RegisterDemoServiceHandlerFromEndpoint error")
}

// grpc
s := grpc.NewServer(grpc.UnaryInterceptor(grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(opentracing.GlobalTracer()))))

3.将traceid引入到log组件中

从Opentracing对Go语言的相关介绍可以得知,trace信息被放在go语言的context里。于是,就有了下面这一段提取traceid的代码。

代码语言:javascript
复制
// 为了使用方便,不修改zap源码,这里利用With函数返回一个SugaredLogger
func WithTrace(ctx context.Context) *zap.SugaredLogger {
 var jTraceId jaeger.TraceID
 if parent := opentracing.SpanFromContext(ctx); parent != nil {
  parentCtx := parent.Context()
  if tracer := opentracing.GlobalTracer(); tracer != nil {
   mySpan := tracer.StartSpan("my info", opentracing.ChildOf(parentCtx))
      // 提取出一个jaeger的traceid
   if sc, ok := mySpan.Context().(jaeger.SpanContext); ok {
    jTraceId = sc.TraceID()
   }
   defer mySpan.Finish()
  }
 }

 return Sugar.With(zap.String(jaeger.TraceContextHeaderName, fmt.Sprint(jTraceId)))
}

4.HTTP请求返回traceid

在拦截器里,解析出trace信息,设置到http的头里。

代码语言:javascript
复制
trace, ok := serverSpan.Context().(jaeger.SpanContext)
if ok {
  w.Header().Set(jaeger.TraceContextHeaderName, fmt.Sprint(trace.TraceID()))
}

示例

我们模拟一个简单的请求

代码语言:javascript
复制
curl --request GET 'http://127.0.0.1:8081/v1/orders'

从返回的结果来看,可以看到Uber-Trace-Id头里有个具体的trace-id,例如5fd1fc3ba1715909。

而在应用代码中,我们添加了一行日志:

代码语言:javascript
复制
func (orderSvc *OrderService) List(ctx context.Context, pageNumber, pageSize int, condition *gormer.OrderOptions) ([]gormer.Order, int64, error) {
 zlog.WithTrace(ctx).Infof("page number is %d", pageNumber)
 // zlog信息
 return orders, count, nil
}

具体的打印如下:

代码语言:javascript
复制
2021-10-22T17:25:05.591+0800 info service/order.go:26 page number is 0 {"uber-trace-id": "5fd1fc3ba1715909"}

虽然格式还不是那么优美,但traceid信息已经填入到了日志中。

至此,调用方只要提供返回的trace-id,我们就可以在程序日志中查找到相应的日志信息,方便针对性地排查问题。

总结

OpenTracing是服务治理非常关键的一环。利用traceid串联一个请求的整个生命周期,能帮助我们快速地排查问题,在实际生产环境上能更快地定位问题。

Github: https://github.com/Junedayday/code_reading Blog: http://junes.tech/ Bilibili: https://space.bilibili.com/293775192 公众号: golangcoding

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go编程点滴 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • v0.6.0:分布式链路追踪-OpenTracing的初步引入
    • 目标
      • 关键技术点
        • 目录构造
        • 1.trace的初始化
        • 2.将opentracing的设置到grpc和grpc-gateway中
        • 3.将traceid引入到log组件中
        • 4.HTTP请求返回traceid
        • 示例
        • 总结
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档