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

简介: 我们从API层到数据库层的链路已经打通,简单的CRUD功能已经可以快速实现。随着模块的增加,我们会越发感受到系统的复杂性,开始关注系统的可维护性。这时,有个名词会进入我们的视野:**分布式链路追踪**

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

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

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

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/ 这篇中已有详细的讲解

目录构造

--- 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包里的全局变量中。

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。

// 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的代码。

// 为了使用方便,不修改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的头里。

trace, ok := serverSpan.Context().(jaeger.SpanContext)
if ok {
   
  w.Header().Set(jaeger.TraceContextHeaderName, fmt.Sprint(trace.TraceID()))
}

示例

我们模拟一个简单的请求

curl --request GET 'http://127.0.0.1:8081/v1/orders'

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

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

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
}

具体的打印如下:

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串联一个请求的整个生命周期,能帮助我们快速地排查问题,在实际生产环境上能更快地定位问题。

相关实践学习
分布式链路追踪Skywalking
Skywalking是一个基于分布式跟踪的应用程序性能监控系统,用于从服务和云原生等基础设施中收集、分析、聚合以及可视化数据,提供了一种简便的方式来清晰地观测分布式系统,具有分布式追踪、性能指标分析、应用和服务依赖分析等功能。 分布式追踪系统发展很快,种类繁多,给我们带来很大的方便。但在数据采集过程中,有时需要侵入用户代码,并且不同系统的 API 并不兼容,这就导致了如果希望切换追踪系统,往往会带来较大改动。OpenTracing为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范。OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间。Skywalking基于OpenTracing规范开发,具有性能好,支持多语言探针,无侵入性等优势,可以帮助我们准确快速的定位到线上故障和性能瓶颈。 在本套课程中,我们将全面的讲解Skywalking相关的知识。从APM系统、分布式调用链等基础概念的学习加深对Skywalking的理解,从0开始搭建一套完整的Skywalking环境,学会对各类应用进行监控,学习Skywalking常用插件。Skywalking原理章节中,将会对Skywalking使用的agent探针技术进行深度剖析,除此之外还会对OpenTracing规范作整体上的介绍。通过对本套课程的学习,不止能学会如何使用Skywalking,还将对其底层原理和分布式架构有更深的理解。本课程由黑马程序员提供。
目录
相关文章
|
19天前
|
负载均衡 监控 Dubbo
Java微服务架构设计与实践:构建可伸缩的分布式系统
【4月更文挑战第2天】微服务架构响应现代业务需求,通过拆分大型应用为独立服务实现模块化和可扩展性。Java中的Spring Boot和Dubbo等框架支持服务注册、负载均衡等功能。遵循单一职责、自治性和面向接口原则,每个服务专注特定逻辑,独立部署运行。实际项目中,如电商系统,服务按功能拆分,提升可维护性和扩展性。还需考虑服务通信、数据一致性和监控等复杂话题。Java微服务架构助力构建高效、灵活的应用,应对未来挑战。
Java微服务架构设计与实践:构建可伸缩的分布式系统
|
17天前
|
消息中间件 分布式计算 中间件
秀出天际!阿里甩出的988页分布式微服务架构进阶神仙手册我粉了
秀出天际!阿里甩出的988页分布式微服务架构进阶神仙手册我粉了
|
19天前
|
SpringCloudAlibaba Java 持续交付
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
241 0
|
19天前
|
消息中间件 Go API
基于Go语言的微服务架构实践
随着云计算和容器化技术的兴起,微服务架构成为了现代软件开发的主流趋势。Go语言,以其高效的性能、简洁的语法和强大的并发处理能力,成为了构建微服务应用的理想选择。本文将探讨基于Go语言的微服务架构实践,包括微服务的设计原则、服务间的通信机制、以及Go语言在微服务架构中的优势和应用案例。
|
19天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
145 1
|
19天前
|
算法 关系型数据库 MySQL
Go语言中的分布式ID生成器设计与实现
【5月更文挑战第6天】本文探讨了Go语言在分布式系统中生成全局唯一ID的策略,包括Twitter的Snowflake算法、UUID和MySQL自增ID。Snowflake算法通过时间戳、节点ID和序列号生成ID,Go实现中需处理时间回拨问题。UUID保证全局唯一,但长度较长。MySQL自增ID依赖数据库,可能造成性能瓶颈。选择策略时需考虑业务需求和并发、时间同步等挑战,以确保系统稳定可靠。
121 0
|
19天前
|
缓存 NoSQL Go
Go语言中的分布式锁实现与选型
【5月更文挑战第6天】本文探讨了Go语言中分布式锁的实现,包括Redis、ZooKeeper和Etcd三种方式,强调了选型时的性能、可靠性和复杂度考量。通过代码示例展示了Redis分布式锁的使用,并提出了避免死锁、公平性等问题的策略。结论指出,开发者应根据业务需求选择合适实现并理解底层原理,以确保系统稳定和高效。
144 0
|
19天前
|
NoSQL 算法 Go
Go语言中的分布式事务处理方案
【5月更文挑战第6天】本文探讨了Go语言在分布式事务处理中的应用,包括2PC、3PC和TCC协议。通过示例展示了如何使用Go的`goroutine`和`channel`实现2PC。同时,文章指出了网络延迟、单点故障、死锁和幂等性等常见问题,并提供了相应的解决策略。此外,还以Redis Redlock为例,展示了如何实现分布式锁。理解并实施这些方案对于构建高可用的分布式系统至关重要。
105 0
|
19天前
|
消息中间件 Go API
Golang深入浅出之-Go语言中的微服务架构设计与实践
【5月更文挑战第4天】本文探讨了Go语言在微服务架构中的应用,强调了单一职责、标准化API、服务自治和容错设计等原则。同时,指出了过度拆分、服务通信复杂性、数据一致性和部署复杂性等常见问题,并提出了DDD拆分、使用成熟框架、事件驱动和配置管理与CI/CD的解决方案。文中还提供了使用Gin构建HTTP服务和gRPC进行服务间通信的示例。
45 0
|
19天前
|
Prometheus 监控 Cloud Native
Golang深入浅出之-Go语言中的分布式追踪与监控系统集成
【5月更文挑战第4天】本文探讨了Go语言中分布式追踪与监控的重要性,包括追踪的三个核心组件和监控系统集成。常见问题有追踪数据丢失、性能开销和监控指标不当。解决策略涉及使用OpenTracing或OpenTelemetry协议、采样策略以及聚焦关键指标。文中提供了OpenTelemetry和Prometheus的Go代码示例,强调全面可观测性对微服务架构的意义,并提示选择合适工具和策略以确保系统稳定高效。
149 5