草庐IT

Go微服务框架go-kratos实战学习05:分布式链路追踪 OpenTelemetry, jaeger 使用

九卷技术录--关注技术架构,管理,产品 2023-03-28 原文

一、分布式链路追踪发展简介

1.1 分布式链路追踪介绍

关于分布式链路追踪的介绍,可以查看我前面的文章 微服务架构学习与思考(09):分布式链路追踪系统-dapper论文学习(https://www.cnblogs.com/jiujuan/p/16097314.html) 。

这里的 OpenTelemetry 有一段发展历程。

APM(Application Performance Monitoring) 和 Distributed Tracing(分布式跟踪),后者是前者的子集。

微服务架构流行起来后,为了监控和定位微服务中请求链路过长导致的定位和监控问题,分布链路监控也蓬勃发展起来。出现了

很多有名的产品,比如:Jaeger,Pinpoint,Zipkin,Skywalking 等等。这里有个问题,就是每家都有自己的一套数据采集标准和SDK。

为了统一这些标准,国外的人们就创建了 OpenTracingOpenCensus 2 个标准。最先出现的是 OpenTracing。为了统一标准,后来两者合并为 OpenTelemetry

1.2 OpenTracing

OpenTracing 制定了一套与平台无关、厂商无关的协议标准,使得开发人员能够方便的添加或更换底层APM的实现。

它是 CNCF 的项目。OpenTracing 协议的产品有 Jaeger、Zipkin 等等。

OpenTracing 数据模型

  • Trace(s):

Trace(s) 在 OpenTracing 中是被 spans 隐式定义的。一个 trace 可以被认为是由一个或多个 span 组成的有向无环图。

比如,下图示例就表示一个 trace 由 8 个 span 组成,也就是一次链路追踪由 8 个 span 组成:

单个 trace(链路) 中 span 之间的关系


        [Span A]  ←←←(the root span)
            |
     +------+------+
     |             |
 [Span B]      [Span C] ←←←(Span C is a `ChildOf` Span A)
     |             |
 [Span D]      +---+-------+
               |           |
           [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                       ↑
                                       ↑
                                       ↑
                         (Span G `FollowsFrom` Span F)

用时间轴来可视化这次链路追踪图,更容易理解:

Temporal relationships between Spans in a single Trace


––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

 [Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··] [Span G··] [Span H··]

(来自:https://opentracing.io/specification/)

  • Span:

Span 是一次链路追踪里的基本组成元素,一个 Span 表示一个独立工作单元,比如一次 http 请求,一次函数调用等。每个 span 里元素:

  • An operation name,服务/操作名称
  • A start timestamp,开始时间
  • A finish timestamp,结束时间
  • Span Tags,key:value 数据形式,用户自定义的标签,主要用途是链路记录信息的查询过滤。
  • Span Logs,key:value 数据形式,主要用途是记录某些事件和事件发生的时间。
  • SpanContext 看下面解释
  • References,对 0 或 更多个相关 span 的引用(通过 SpanContext 来引用)
  • SpanContext:

SpanContext 携带跨进程(跨服务)通信的数据。它的组成:

  • 在系统中表示 span 的信息。比如 span_id, trace_id。
  • Baggage Items,为整条追踪链路保存跨进程(跨服务)的数据,数据形式是 key:value
  • References

多个 span 中的对应关系。OpenTracing 目前定义了 2 种关系:ChildOfFollowsFrom

  • ChildOf,一个子 span 可能是父 span 的 ChildOf
    [-Parent Span---------]
         [-Child Span----]

    [-Parent Span--------------]
         [-Child Span A----]
          [-Child Span B----]
        [-Child Span C----]
         [-Child Span D---------------]
         [-Child Span E----]
  • FollowsFrom,一些父 span 不依赖任何的子 span
    [-Parent Span-]  [-Child Span-]


    [-Parent Span--]
     [-Child Span-]


    [-Parent Span-]
                [-Child Span-]

(来自:https://opentracing.io/specification/)

1.3 OpenCensus

为什么又出现个 OpenCensus 这个项目?因为它有个好爹:google。要知道分布式跟踪的基础论文就是谷歌提出。

其实,刚开始它并不是要抢 OpenTracing 的饭碗,它只是为了把 Go 语言的 Metrics 采集、链路跟踪与 Go 语言自带的

profile 工具打通,统一用户的使用方式。但是随着项目发展,它也想把链路相关的统一一下。它不仅要做 Metrics 基础指标监控,

还要做 OpenTracing 的老本行:分布式跟踪。

1.4 OpenTracing 与 OpenCensus 对比

2 者功能对比

1.5 OpenTelemetry

这样出现 2 个标准也不是个事啊,如是就出现了 OpenTelemetry,它把 2 者合并在一起了。

OpenTelemetry 的核心工作目前主要集中在 3 个部分:

  1. 规范的制定和协议的统一,规范包含数据传输、API 的规范,协议的统一包含:HTTP W3C 的标准支持及GRPC等框架的协议标准
  2. 多语言 SDK 的实现和集成,用户可以使用 SDK 进行代码自动注入和手动埋点,同时对其他三方库(Log4j、LogBack等)进行集成支持;
  3. 数据收集系统的实现,当前是基于 OpenCensus Service 的收集系统,包括 Agent 和 Collector。

(1.4 1.5来自: https://github.com/open-telemetry/docs-cn)

OpenTelemetry 的最终形态就是实现 Metrics、Tracing、Logging 的融合。

OpenTelemetry 整体架构图:

(来自:https://opentelemetry.io/docs/)

Tracing API 中几个重要概念:

  • TracerProvider:是 API 的入口点,提供了对 tracer 的访问。在代码里主要是创建一个 Tracer,一般是第三方分布式链路管理软件提供具体实现。默认是一个空的 TracerProvider(""),虽然也创建 Tracer,但是内部不会执行数据流传输逻辑。
  • Tracer:负责创建 span,一个 tracer 表示一次完整的追踪链路。tracer 由一个或多个 span 组成。跟上面的 OpenTracing 数据模型很像,所以说是两者合并。
  • Span:一次链路追踪操作里的基本操作元素。比如一次函数调用,一次 http 请求。

里面还有很多详细介绍:https://opentelemetry.io/docs/reference/specification/trace/api/

还有一个数据采样,https://www.cnblogs.com/jiujuan/p/16097314.html - 前面学习 dapper 论文的这篇文章有介绍。

小结:

一条链路追踪信息:

有一条链路 trace,它是由一个或多个 span 组成, span 里会记录各种链路中的信息,跨进程的信息,各种 span 之间的关系。

使用哪种链路管理软件,则由 traceprovider 来设置。可以是 Jaeger,Pinpoint,Zipkin,Skywalking 等等。

span 中的信息收集到链路管理软件,然后可以用图来展示记录的链路信息和链路之间的关系。

二、jaeger 简介

Jaeger 是受到 Dapper 和 OpenZipkin 启发,是 Uber 开发的一款分布式链路追踪系统。

它用于监控微服务和排查微服务中出现的故障。

jaeger 架构图

(来自:https://www.jaegertracing.io/docs/1.35/architecture/)

jaeger 安装:

参考我前面文章 :https://www.cnblogs.com/jiujuan/p/13235748.html docker all-in-one 安装

三、kratos 中链路追踪使用代码

前面介绍了那么多,应该对 opentelemetry 大致有了一个了解。下面就在 kratos 中使用 opentelemetry。

这里使用 jaeger 作为链路追踪的管理软件。

go 1.17

go-kratos 2.2.1

jaeger 1.35

下面代码来自 go-kratos 官方例子。

server 端

在 main.go 中,有 grpc server 和 http server。

第一步,设置 TraceProvider()

// get trace provider
func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
	// create the jaeger exporter
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
	if err != nil {
		return nil, err
	}

	// New trace provider
	tp := tracesdk.NewTracerProvider(
		tracesdk.WithSampler(tracesdk.AlwaysSample()),
		// always be sure to batch in production
		tracesdk.WithBatcher(exp),
		// Record information about this application in an Resource.
		tracesdk.WithResource(
			resource.NewWithAttributes(
				semconv.SchemaURL,
				semconv.ServiceNameKey.String(Name), // service name,实例名称
				attribute.String("env", Env),        // environment
				attribute.String("ID", Version),     // version
			)),
	)
	return tp, nil
}

第二步,grpc server

url := "http://jaeger:14268/api/traces"
if os.Getenv("jaeger_url") != "" {
    url = os.Getenv("jeager_url")
}

tp, err := tracerProvider(url) // tracer provider
if err != nil {
    log.Error(err)
}

s := &server{}

// grpc server
grpcSrv := grpc.NewServer(
    grpc.Address(":9000"),
    grpc.Middleware(
        middleware.Chain(
            recovery.Recovery(),
            tracing.Server(tracing.WithTracerProvider(tp)), //设置trace,传入 trace provider
            logging.Server(logger),
        ),
    ),
)

第三步,http server

func main() {
	logger := log.NewStdLogger(os.Stdout)
	log := log.NewHelper(logger)

	tp, err := tracerProvider("http://jaeger:14268/api/traces")
	if err != nil {
		log.Error(err)
	}

	httpSrv := http.NewServer(
		http.Address(":8080"),
		http.Middleware(
			middleware.Chain(
				recovery.Recovery(),
				// Configuring tracing middleware
				tracing.Server(
					tracing.WithTracerProvider(tp), // 提供 trace provider
				),
				logging.Server(logger),
			),
		),
	)
	s := &server{}
	pb.RegisterUserHTTPServer(httpSrv, s)

	app := kratos.New(
		kratos.Name(Name),
		kratos.Server(
			httpSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Error(err)
	}
}

client 端

grpc client 和 http client

grpc client:

// create grpc conn
// only for demo, use single instance in production env
conn, err := grpc.DialInsecure(ctx,
   grpc.WithEndpoint("127.0.0.1:9000"),
   grpc.WithMiddleware(middleware.Chain(
	   tracing.Client( //trace client
		   tracing.WithTracerProvider(s.tracer),
	   ),
	   recovery.Recovery(),
   )),
   grpc.WithTimeout(time.Second*2),
  )
if err != nil {
    return nil, err
}

http client:

http.NewClient(ctx, http.WithMiddleware(
    tracing.Client(
        tracing.WithTracerProvider(s.tracer),
    ),
))

四、在student项目里使用链路追踪

在前面的 go-kratos gorm 练习项目中加入链路追踪。
https://github.com/jiujuan/go-kratos-demos/tree/master/student。

4.1 编写代码

第一步,在 internal/server 下新建 pkg/tracer 文件夹,tracer.go 程序

把 tracer.go 作为一个独立文件

package tracer

import (
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/sdk/resource"
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
)

type Conf struct {
	Name string
	Env  string
	Ver  string
	Url  string
}

func NewConf(name, env, ver, url string) *Conf {
	return &Conf{
		Name: name,
		Env:  env,
		Ver:  ver,
		Url:  url,
	}
}

func (c *Conf) TracerProvider() (*tracesdk.TracerProvider, error) {
	exp, err := jaeger.New(
		jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Url)),
	)
	if err != nil {
		return nil, err
	}

	tp := tracesdk.NewTracerProvider(
		tracesdk.WithSampler(tracesdk.AlwaysSample()),
		tracesdk.WithBatcher(exp),
		tracesdk.WithResource(
			resource.NewWithAttributes(
				semconv.SchemaURL,
				semconv.ServiceNameKey.String(c.Name),
				attribute.String("env", c.Env),
				attribute.String("ver", c.Ver),
			)),
	)
	return tp, nil
}

第二步,在 internal/server/grpc.go:NewGRPCServer() 函数加入链路追踪代码:

var opts = []grpc.ServerOption{
	grpc.Middleware(
		recovery.Recovery(),
		tracing.Server(), // 链路追踪
	),
}

第三步,在 internal/server/grpc.go:NewHTTPServer() 函数加入链路追踪代码:

var opts = []http.ServerOption{
	http.Middleware(
		recovery.Recovery(),
		tracing.Server(), // 链路追踪
	),
}

第四步,在 cmd/student/main.go 加入如下代码:

// 配置,启动链路追踪
url := "http://192.168.0.103:14268/api/traces"
Name = "kratos.service.student"
id = "kratos.id.student.1"
Version = "test-V0.0.1"
traceconf := tracer.NewConf(Name, id, Version, url)
tp, _ := traceconf.TracerProvider()
otel.SetTracerProvider(tp) // 为全局链路追踪

上面这段程序可以用 wire 配置程序。

完整代码地址:完整代码地址:https://github.com/jiujuan/go-kratos-demos/tree/master/student

4.2 测试

请先自行安装 jaeger。

可以用 docker all-in-one 快速安装,详细命令请参考:https://www.cnblogs.com/jiujuan/p/13235748.html

第一步,启动kratos服务

$ cd cmd/student
$ kratos run
INFO msg=config loaded: config.yaml format: yaml
INFO msg=[gRPC] server listening on: [::]:9000
INFO msg=[HTTP] server listening on: 127.0.0.1:8080

第二步,使用 curlie - https://github.com/rs/curlie 测试:

$ curlie  http://127.0.0.1:8080/student/3
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 34

{
    "name": "jimmy",
    "status": 0,
    "id": 3
}

第三步:打开 jaeger web ui 查看结果

http://192.168.0.103:16686/search

完整代码地址:完整代码地址:https://github.com/jiujuan/go-kratos-demos/tree/master/student


也欢迎到我的公众号:九卷技术录-kratos实战学习05:分布式链路追踪 交流

五、参考

有关Go微服务框架go-kratos实战学习05:分布式链路追踪 OpenTelemetry, jaeger 使用的更多相关文章

  1. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  4. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  5. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  6. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  7. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  8. ruby-on-rails - Textmate 'Go to symbol' 相当于 Vim - 2

    在Railcasts上,我注意到一个非常有趣的功能“转到符号”窗口。它像Command-T一样工作,但显示当前文件中可用的类和方法。如何在vim中获取它? 最佳答案 尝试:helptags有各种程序和脚本可以生成标记文件。此外,标记文件格式非常简单,因此很容易将sed(1)或类似的脚本组合在一起,无论您使用何种语言,它们都可以生成标记文件。轻松获取标记文件(除了下载生成器之外)的关键在于格式化样式而不是实际解析语法。 关于ruby-on-rails-Textmate'Gotosymbol

  9. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  10. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

随机推荐