什么是分布式追踪
分布式系统
多台计算机通过网络组成了一个庞大的系统,这个系统即是分布式系统。
在微服务或者云原生开发中,一般认为分布式系统是通过各种中间件/服务网格连接的,这些中间件提供了共享资源、功能(API等)、文件等,使得整个网络可以当作一台计算机进行工作。
分布式追踪
在分布式系统中,用户的一个请求会被分发到多个子系统中,被不同的服务处理,最后将结果返回给用户。用户发出请求和获得结果这段时间是一个请求周期。
当我们购物时,只需要一个很简单的过程:
下单 -> 付款 -> 等待收货
图中出现了很多箭头,这些箭头指向了下一步要流经的服务/子系统,这些箭头组成了链路网络。
在一个复杂的分布式系统中,任何子系统出现性能不佳的情况,都会影响整个请求周期。根据上图,我们设想:
- 系统中有可能每天都在增加新服务或删除旧服务,也可能进行升级,当系统出现错误,我们如何定位问题?
当用户请求时,响应缓慢,怎么定位问题?
服务可能由不同的编程语言开发,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")
}