什么是分布式追踪

分布式系统

多台计算机通过网络组成了一个庞大的系统,这个系统即是分布式系统。

在微服务或者云原生开发中,一般认为分布式系统是通过各种中间件/服务网格连接的,这些中间件提供了共享资源、功能(API等)、文件等,使得整个网络可以当作一台计算机进行工作。

分布式追踪

在分布式系统中,用户的一个请求会被分发到多个子系统中,被不同的服务处理,最后将结果返回给用户。用户发出请求和获得结果这段时间是一个请求周期。

当我们购物时,只需要一个很简单的过程:

下单 -> 付款 -> 等待收货

图中出现了很多箭头,这些箭头指向了下一步要流经的服务/子系统,这些箭头组成了链路网络。

在一个复杂的分布式系统中,任何子系统出现性能不佳的情况,都会影响整个请求周期。根据上图,我们设想:

  1. 系统中有可能每天都在增加新服务或删除旧服务,也可能进行升级,当系统出现错误,我们如何定位问题?

  2. 当用户请求时,响应缓慢,怎么定位问题?

  3. 服务可能由不同的编程语言开发,1、2 定位问题的方式,是否适合所有编程语言?

分布式追踪有什么用呢

随着微服务和云原生开发的兴起,越来越多应用基于分布式进行开发,但是大型应用拆分为微服务后,服务之间的依赖和调用变得越来越复杂,这些服务是不同团队、使用不同语言开发的,部署在不同机器上,他们之间提供的接口可能不同(gRPC、Restful api等)。

为了维护这些服务,软件领域出现了 Observability 思想,在这个思想中,对微服务的维护分为三个部分:

  • 度量(Metrics):用于监控和报警; (pprof,promethues+grafana)
  • 分布式追踪(Tracing):用于记录系统中所有的跟踪信息;(Dapper,zipkin,skywalking)
  • 日志(Logging):记录每个服务中离散的信息;(ELK elastic+logstash+kibana+kafka+filebeat)

这三部分并不是独立开来的,例如 Metrics 可以监控 Tracing 、Logging 服务是否正常运行。Tacing 和 Metrics 服务在运行过程中会产生日志。

近年来,出现了 APM 系统,APM 称为 应用程序性能管理系统,可以进行 软件性能监视和性能分析。APM 是一种 Metrics,但是现在有融合 Tracing 的趋势。

回归正题,分布式追踪系统(Tracing)有什么用呢?这里可以以 zipkin 举例,它可以:

  • 分布式跟踪信息传递
  • 分布式事务监控
  • 服务依赖性分析
  • 展示跨进程调用链
  • 定位问题
  • 性能优化

代码示例

gin+zipkin

/**
 * Createby GoLand
 * User xzw jsjxzw@163.com
 * Date 2020/8/7
 * Time 4:48 下午
 */

package main

import (
    "flag"
    "log"
    "net/http"

    _ "net/http/pprof"

    "github.com/prometheus/client_golang/prometheus"
    metric "imooc.com/ccmouse/learngo/api/prometheus"

    "github.com/prometheus/client_golang/prometheus/promhttp"

    "github.com/gin-gonic/gin"
    "imooc.com/ccmouse/learngo/api/user"

    _ "github.com/mkevac/debugcharts"

    "github.com/opentracing/opentracing-go"
    zkOt "github.com/openzipkin-contrib/zipkin-go-opentracing"
    "github.com/openzipkin/zipkin-go"
    zkHttp "github.com/openzipkin/zipkin-go/reporter/http"
)

// 第一步: 开一个全局变量
var zkTracer opentracing.Tracer

var (
    listenAddr       = flag.String("web.listen-port", "9000", "An port to listen on for web interface and telemetry.")
    metricsPath      = flag.String("web.telemetry-path", "/metrics", "A path under which to expose metrics.")
    metricsNamespace = flag.String("metric.namespace", "ECSDATA", "Prometheus metrics namespace, as the prefix of metrics name")
)

func main() {

    // 第二步: 初始化 tracer
    {
        reporter := zkHttp.NewReporter("http://localhost:9411/api/v2/spans")
        defer reporter.Close()
        endpoint, err := zipkin.NewEndpoint("main3", "localhost:8888")
        if err != nil {
            log.Fatalf("unable to create local endpoint: %+v\n", err)
        }
        nativeTracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint))
        if err != nil {
            log.Fatalf("unable to create tracer: %+v\n", err)
        }
        zkTracer = zkOt.Wrap(nativeTracer)
        opentracing.SetGlobalTracer(zkTracer)
    }

    go func() {
        flag.Parse()
        metrics := metric.NewMetrics(*metricsNamespace)
        registry := prometheus.NewRegistry()
        registry.MustRegister(metrics)
        http.Handle(*metricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
        log.Println(http.ListenAndServe(":9100", nil))
    }()
    Router()
}

func Router() {
    router := gin.Default()

    // 第三步: 添加一个 middleWare, 为每一个请求添加span
    router.Use(func(c *gin.Context) {
        span := zkTracer.StartSpan(c.FullPath())
        defer span.Finish()
        c.Next()
    })

    router.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"ping": "pong"})
    })
    router.GET("/user", user.InitPage)
    router.GET("/user/list", user.ListUser)
    router.POST("/user/create", user.CreateUser)
    router.GET("/user/detail/:id", user.DetailUser)
    router.PATCH("/user/:id", user.UpdateUser)
    router.DELETE("/user/:id", user.DeleteUser)

    router.Run(":8888")
}

Scroll to Top