全链路监控

1. 全链路监控的兴起与发展

当代的互联网的服务,通常都是用复杂的、大规模分布式集群来实现的。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具,全链路监控组件就在这样的问题背景下产生了。

1.1 组件

2010年,google发表了一篇公开论文Google Dapper,这篇著名论文阐述了如何在超大规模系统上建设低损耗、应用级透明、大范围部署的链路追踪服务,在该文章的启发下,业界诞生了很多用于分布式链路追踪的开源组件,大部分组件都参考了google dapper的设计理念。发展至今,业界比较流行的开源组件有如下几种:

名称开源公司git地址开发语言
pinpointnaverhttps://github.com/naver/pinpointjava
zipkintwiterhttps://github.com/openzipkin/zipkinjava
jaegeruberhttps://github.com/jaegertracing/jaegergo
skywalking国内作者转Apachehttps://github.com/apache/incubator-skywalkingjava

1.2 标准

在 Dapper 的启发下,业界诞生了很多用于分布式链路追踪的开源组件,为了保持对链路中每一个环节的记录与匹配,不仅需要在应用内部对跟踪信息进行传递,还需要让跟踪信息跨越不同的应用以及不同的分布式组件。这需要制定一套统一的标准,让微服务体系中的所有应用遵循这套标准来实现跟踪信息的描述和传递。
业内先后出现了两套比较著名的标准OpenCensus、OpenTracing,这两套标准都抽象出一套与编程语言以及业务逻辑无关的接口,用于对链路追踪的统一管理,为全链路监控的发展做出了巨大贡献。但是这两个项目的最大问题就是他们是两个项目,这两个项目并没有在一起工作,也没有为相互兼容而努力。世界上有两个相似但不完全相同的项目,这给开发者带来了困惑和不确定性。于是在2019年,在由谷歌、微软、Uber等公司组成的委员会的牵头下,OpenCensus和OpenTracing合并为一套标准,它就是 Open Telemetry。OpenTelemetry直接向后兼容OpenTracing和OpenCensus,成为了现在业内普遍使用的标准。

2. 全链路监控的设计理念

2.1 设计目标

分布式链路监控的组件设计时,通常需要从以下方面做考虑:

  • 性能消耗:服务调用埋点本身会带来性能损耗,通常组件的性能消耗越小越好,并且可以实现采样机制,通过配置采样率来控制性能消耗。
  • 应用级的透明:作为组件,应该尽可能少的甚者无侵入其他业务系统,对于使用方做到透明易用。
  • 数据分析:提供功能齐全的分析后台,能够从多维度对数据进行分析
  • 可扩展性:必须支持分布式部署,以支持巨量的trace日志分析

2.2 设计模型

在Google Dapper的设计中,一次完整的调用被称为trace,而其中一条链路的调用被称为span,一个trace是由许多span组成的一个树形结构,而一个span又包含了很多事件相关信息,被称为Annotation(注解)。

2.2.1 trace

race表示一次完整的跟踪,通常从请求到服务器开始,到服务器返回response结束,其通常包含一个唯一标识TraceId,你可以通过此TraceId来在后台查看此次调用的链路情况,下面是一个调用例子:
在这里插入图片描述

2.2.2 span

span是链路追踪的基本组成单元,其也包含了唯一标识SpanId,一个span可以表示追踪任何事件,不仅限于rpc调用、http调用、DB调用,也可以表示一次函数调用甚至部分函数调用,具体情况视使用情况而定。
由于trace是由span组成的树形结构,所以一个span不仅有自己的SpanId,还包含了parent_id以标识其父span,如果一个span没有父ID被称为root span。一个trace的所有span都共用一个TraceId。另外span还包含其他描述信息,比如时间戳以及由key-value形式存储的特定事件tag(Annotation)信息。
通常一个Span至少包含下面的信息:

type Span struct {
    TraceID    int64
    SpanID     int64
    ParentID   int64
    StartTime  time.Time
    EndTime    time.Time
    Annotation []Annotation
}

把同一TraceID的Span收集起来,按时间排序timeline,把ParentID串起来就是调用栈,最后后台上会展示类似下面图片所示的结构:
在这里插入图片描述

2.2.3 调用过程追踪

每一次追踪的生成过程如下所示:

  1. 请求到来生成一个全局TraceID,通过TraceID可以串联起整个调用链,一个TraceID代表一次请求。
  2. 每一次链路的调用会生成一个span,span中记录了ParentId和SpanId,一个没有ParentId的span成为root span,可以看成调用链入口。
  3. 整个调用过程中每个请求都要透传TraceID和SpanID,每个服务将该次请求附带的TraceId作为自己的TraceId,并将该次请求附带的SpanID作为ParentId,并且生成自己的SpanID,并记录其他信息。
  4. 要查看某次完整的调用,只要根据TraceID查出所有调用记录,然后通过parent id和span id组织起整个调用父子关系。

2.3 接入模式

接入模式通常分为SDK和探针模式两种:

  • SDK: 通过引入链路追踪 SDK 自动生成链路数据,并自动上报。对框架或业务代码会有侵入,有时监控逻辑的注入会比较复杂。
  • 探针模式: 探针方式不需要在代码编译前引入 SDK ,而是在应用运行的过程中,通过一个 Agent 动态的拦截底层框架的行为,从而自动注入监控逻辑。像 Java 这样的编程语言可以通过字节码注入实现链路信息采集。这是一种最开发者最友好的方式,不需要任何代码层面的改动,但并不是每一种编程语言都能提供探针机制,因此 SDK 方式也被很多全链路监控组件采用

各组件的指标对比
在这里插入图片描述

3. OpenTelemetry Go SDK

3.1 Tracing Api

Tracing Api主要由下面几部分组成:

  • TracerProvider: 提供tracer
  • Tracer: 创建span
  • Span: 监控某个操作
  • SpanContext与Propagator: 用于传递Span信息
  • SpanExporter: 用于导出Span数据

3.1.1 TracerProvider

TracerProvider用于获取一个Tracer,其结构如下:

type TracerProvider struct {
    mu             sync.Mutex
    // tracer列表
    namedTracer    map[instrumentation.Library]*tracer
    // span处理器
    spanProcessors atomic.Value
    // 采样器
    sampler     Sampler
    // id生成器
    idGenerator IDGenerator
    // span的限制配置
    spanLimits  SpanLimits
    resource    *resource.Resource
}
// 获取一个tracer
func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {...}
// 关闭TracerProvider,同时会关闭其生成的所有tracer
func (p *TracerProvider) Shutdown(ctx context.Context) error {...}

同时在包go.opentelemetry.io/otel提供了一个全局TracerProvider,并定义了相应接口:

// 获取全局TracerProvider
func GetTracerProvider() trace.TracerProvider {...}
// 设置全局TracerProvider
func SetTracerProvider(tp trace.TracerProvider) {...}
// 直接使用全局TracerProvider来获取Tracer
func Tracer(name string, opts ...trace.TracerOption) trace.Tracer {...}

3.1.2 Tracer

Tracer用于创建Span:

type Tracer interface {
    // 用于创建一个Span
    Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span)
}

通常Tracer在创建时不会指定配置,而是直接使用TracerProvider的配置。

3.1.3 Span

Span用于记录一个操作的相应信息:

type Span interface {
    //结束Span
    End(options ...SpanEndOption)
    AddEvent(name string, options ...EventOption)
    IsRecording() bool
    RecordError(err error, options ...EventOption)
    // 将Span的信息以SpanContext的形式返回
    SpanContext() SpanContext
    // 设置Span的状态
    SetStatus(code codes.Code, description string)
    // 设置Span的名字
    SetName(name string)
    // 设置Span的信息
    SetAttributes(kv ...attribute.KeyValue)
    // 获取相关的TracerProvider
    TracerProvider() TracerProvider
}

一个常见的Span内部结构如下:

type recordingSpan struct {
    mu sync.Mutex
    // 父Span的信息
    parent trace.SpanContext
    // Span的种类
    spanKind trace.SpanKind
    // 名字
    name string
    // 开始时间
    startTime time.Time
    // 结束时间
    endTime time.Time
    // 状态
    status Status
    childSpanCount int
    // 自身的Span信息
    spanContext trace.SpanContext
    // 标注信息
    attributes        []attribute.KeyValue
    droppedAttributes int
    events evictedQueue
    links evictedQueue
    executionTracerTaskEnd func()
    tracer *tracer
}

3.1.4 SpanContext与Propagator

SpanContext用于记录Span的信息,其实现了context.Context的接口;而Propagator用于传播SpanContext
SpanContext结构如下,其最主要的信息是traceID和spanID:

type SpanContext struct {
    traceID    TraceID
    spanID     SpanID
    traceFlags TraceFlags
    traceState TraceState
    remote     bool
}

而Propagator通常有如下接口:

type TextMapPropagator interface {
    // 将context中的信息注入map中
    Inject(ctx context.Context, carrier TextMapCarrier)
    // 将Map中的信息读出并放入context中
    Extract(ctx context.Context, carrier TextMapCarrier) context.Context
    // 获取所有fields
    Fields() []string
}

3.1.5 SpanExporter

SpanExporter用于导出Span数据,其接口如下:

type SpanExporter interface {
    // 导出Spans
    ExportSpans(ctx context.Context, spans []ReadOnlySpan) error
    // 关闭,会阻塞等待至所有Spans被导出
    Shutdown(ctx context.Context) error
}

Open Telemetry支持的数据导出方式如下:
在这里插入图片描述

3.2 使用方式

在使用Tracer之前,需要先配置TracerProvider,然后从TracerProvider中取出一个Tracer,然后用Tracer生成一个Span用于记录相应的操作。
创建文件trace_example_test.go,将下面代码复制进去,启动测试用例。

package instrument
 
import (
    "context"
    "os"
    "testing"
    "time"
 
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/resource"
    sdkTrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
 
func TestTrace(t *testing.T) {
    // 创建exporter,数据直接输出至标准输出
    exporter, err := stdouttrace.New(
        stdouttrace.WithWriter(os.Stdout),
        stdouttrace.WithPrettyPrint(),
        stdouttrace.WithoutTimestamps(),
    )
    if err != nil {
        panic(err)
    }
 
    // 配置traceProvider
    tracerProvider := sdkTrace.NewTracerProvider(
        // 配置数据导出
        sdkTrace.WithBatcher(exporter),
        // 配置基础信息
        sdkTrace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("test-service"),
        )),
    )
 
    // 获取tracer
    tracer := tracerProvider.Tracer("test")
 
    // 开始span
    ctx, span := tracer.Start(context.Background(), "sleep")
    // sleep ls
    time.Sleep(time.Second * 1)
    // 结束span
    span.End()
 
    // 关闭
    tracerProvider.Shutdown(ctx)
}

标准输出应能看到输出:

{
    "Name": "sleep",
    "SpanContext": {
        "TraceID": "8b74ae6d227e052ec64afc6961e3da7b",
        "SpanID": "a47174ce4a217248",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "00000000000000000000000000000000",
        "SpanID": "0000000000000000",
        "TraceFlags": "00",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "0001-01-01T00:00:00Z",
    "EndTime": "0001-01-01T00:00:00Z",
    "Attributes": null,
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 0,
    "Resource": [
        {
            "Key": "service.name",
            "Value": {
                "Type": "STRING",
                "Value": "test-service"
            }
        }
    ],
    "InstrumentationLibrary": {
        "Name": "test",
        "Version": "",
        "SchemaURL": ""
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/430032.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CentOS 7操作系统安装教程

CentOS 7操作系统安装教程 CentOS 7是一款功能强大、稳定可靠的操作系统,适用于服务器、桌面等多种场景。下面将介绍CentOS 7的安装教程。 准备工作 下载CentOS 7镜像文件:https://mirrors.tuna.tsinghua.edu.cn/centos/7/isos/x86_64/准备安装介质&am…

【新书推荐】13.2 应用举例

本节内容:磁盘文件管理功能号调用应用举例。 ■例1:显示文本文件内容t13-1.asm。 ■例2:将键盘输入字符存入文件t13-2.asm。 ■例3:文件拼接t13-3.asm。 13.2.1 例1:显示文本文件内容 动手实验93:写一个…

Centos 9 安装 k8s

为了尽可能契合生产环境的部署情况,这里用kubeadm安装集群,同时方便跟随笔记一步步实践的过程,也更加了解k8s的一些特性和基础知识。 先决条件 这里将通过虚拟机安装3台centos stream 9服务器,并组成kubeneters集群(…

如何在MinIO系统中进行配置并结合内网穿透实现公网远程连接上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统,它可以100%的运行在标准硬件上,即X86等…

ROS2中std_msgs/msg/Header 数据含义及使用

ROS2中std_msgs/msg/Headerr 数据含义及使用 ROS官方消息说明数据说明使用ros2标准的Header案例代码解释测试结果 ROS官方消息说明 ROS2中std_msgs消息包含类型 https://docs.ros2.org/latest/api/std_msgs/msg/std_msgs/msg/Header Message std_msgs/msg/Header数据格式&…

“零碳未来”:引领全球向低碳经济转型

全球环境基金(GEF),这个由183个国家和地区组成的国际合作组织,是世界银行1990年创建的实验项目,一直致力于支持环境友好型项目,推动全球环境改善。而“零碳未来”不仅是一个由全球环境基金(GEF)创建的跨越国界的全新交易平台,更是一个致力于推动全球向低碳经济转型的零碳排放生…

ChromeDriver全版本下载教程

确定自己的Chrome版本 step1. 打开Chrome浏览器右上角的三个点,再点击设置 step2. 在设置中点击“关于Chrome”,圈起来的红框即为当前Chrome版本,我的版本就是121.0.6167.185 在json中查找自己对应ChromeDriver版本下载链接 一般教程会让你…

2_SQL

文章目录 SQL数据完整性实体完整性域完整性参照完整性default(默认值)comment(注释) 多表设计一对一一对多多对多数据库三大范式第一范式:原子性第二范式:唯一性第三范式:数据的冗余 多表查询连…

【问题】解决c++调用exosip是报错,无法解析的外部符号 _DnsQueryConfig@24

问题描述: error LNK2019: 无法解析的外部符号 _DnsQueryConfig24,函数 _eXosip_dnsutils_naptr 中引用了该符号 DnsQueryConfig是windowAPI,可查阅msdn。 #pragma comment(lib,"ws2_32.lib") #pragma comment(lib,"Dnsapi.…

[最佳实践] conda环境内安装cuda 和 Mamba的安装

Mamba安装失败的过程中,causal-conv1d安装报错为连接超时 Mamba安装 主要故障是 pip install causal-conv1d1.2.0 安装失败 安装实践比较长,请耐心等待 解决方案 受到启发运行Mamba项目时无法直接用pip install安装causal_conv1d和mamba_ssm_pip insta…

突显借力之美:打造令人印象深刻的平面设计师简历!

作为一名平面设计师,简历不仅是展示你设计能力的重要工具,也是展示你专业素质和态度的重要参考。因此,如何写一份能让HR眼前一亮的简历已经成为每个平面设计师都需要掌握的技能。在本文中,我们将从四个方面探讨如何写好平面设计师…

如何准备2024年汉字小达人:历年考题练一练-18道选择题解析

距离2024年第11届汉字小达人比赛还有八个多月的时间,建议如果有可能都让孩子参加一下,无需报名费,如果没时间准备也可以直接上阵参赛,检验一下孩子语文字、词、成语和古诗文方面的掌握情况。一方面可以激发孩子学习语文的兴趣&…

cache基础

基本概念: cache line —— 缓存行,一般为 32B、64B 。way —— 路。set —— 组。VIPT —— Virtual Index Physical Tag。 Index来自虚拟地址。PIPT —— Physical Index Physical Tag。 Index来自物理地址。 cache 更新策略: write thr…

组基轨迹建模 GBTM的介绍与实现(Stata 或 R)

基本介绍 组基轨迹建模(Group-Based Trajectory Modeling,GBTM)(旧名称:Semiparametric mixture model) 历史:由DANIELS.NAGIN提出,发表文献《Analyzing Developmental Trajectori…

R语言简介、环境与基础语法及注释

R语言简介、环境与基础语法及注释 一、R语言1.R语言简介2.R语言官网3.R语言中国的镜像网站4.R语言下载5.R语言的历史 二、R语言环境1.Windows安装1.1 去 R 语言下载的镜像站点的列表下载1.2 选择版本进行下载1.3 点击运行1.4 一路默认,安装完毕! 2.Linux…

Golang Channel 详细原理和使用技巧

1.简介 Channel(一般简写为 chan) 管道提供了一种机制:它在两个并发执行的协程之间进行同步,并通过传递与该管道元素类型相符的值来进行通信,它是Golang在语言层面提供的goroutine间的通信方式.通过Channel在不同的 goroutine中交换数据,在goroutine之间…

yolov9训练

目录 说明 1、下载代码安装新的python环境 2、准备数据 3、修改代码 说明 本文参考该博主的文章,在已经有数据的情况,进行简单总结。需要详细版见原文链接如下:YOLOV9保姆级教程-CSDN博客 1、下载代码安装新的python环境 代码下载&…

阿里巴巴面试题:亿级商品如何存储?

嗨,各位小米粉丝们,欢迎来到小米的科技分享专栏!今天我们要聊的话题可是相当的烧脑,它来自阿里巴巴的一道面试题:亿级商品如何存储?别急,让我一一为你解密! 分库分表 当我们面对需要处理海量数据的情况时,基于 Hash 取模和一致性 Hash 实现分库分表是一个常见且有效…

【机器学习】生成对抗网络GAN

概述 生成对抗网络(Generative Adversarial Network,GAN)是一种深度学习模型架构,由生成器(Generator)和判别器(Discriminator)两部分组成,旨在通过对抗训练的方式生成逼…

【大前端】EChart 多系列柱状图绘制背景图

背景 在ECharts中,设置柱状图背景色,可通过backgroundColor设置,但仅限于单系列柱状图,所以在多系列柱状图中就需要用下面的方式设置 解决方案 1. xAxis.splitArea 如果设置的背景图的宽度占比为100%,则可以使用该方…