.NET Core分布式链路追踪框架的基本实现原理

目录
  • 分布式追踪
    • 什么是分布式追踪
      • 分布式系统
      • 分布式追踪
      • 分布式追踪有什么用呢
  • Dapper
    • 分布式追踪系统的实现
    • 跟踪树和 span
  • Jaeger 和 OpenTracing
    • OpenTracing
    • Jaeger 结构
      • OpenTracing 数据模型
      • Span 格式
      • Trace
    • Span
    • OpenTracing API

分布式追踪

什么是分布式追踪

分布式系统

当我们使用 Google 或者 百度搜索时,查询服务会将关键字分发到多台查询服务器,每台服务器在自己的索引范围内进行搜索,搜索引擎可以在短时间内获得大量准确的搜索结果;同时,根据关键字,广告子系统会推送合适的相关广告,还会从竞价排名子系统获得网站权重。通常一个搜索可能需要成千上万台服务器参与,需要经过许多不同的系统提供服务。

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

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

分布式追踪

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

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

获取优惠劵 -> 下单 -> 付款 -> 等待收货

然而在后台系统中,每一个环节都需要经过多个子系统进行协作,并且有严格的流程。例如在下单时,需要检查是否有优惠卷、优惠劵能不能用于当前商品、当前订单是否符合使用优惠劵条件等。

下图是一个用户请求后,系统处理请求的流程。

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

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

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

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

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

分布式追踪有什么用呢

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

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

  • 度量(Metrics):用于监控和报警;
  • 分布式追踪(Tracing):用于记录系统中所有的跟踪信息;
  • 日志(Logging):记录每个服务只能中离散的信息;

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

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

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

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

Jaeger 需要结合后端进行结果分析,jaeger 有个 Jaeger UI,但是功能并不多,因此还需要依赖 Metrics 框架从结果呈现中可视化,以及自定义监控、告警规则,所以很自然 Metrics 可能会把 Tracing 的事情也做了。

Dapper

Dapper 是 Google 内部使用的分布式链路追踪系统,并没有开源。

Dapper 用户接口:

分布式追踪系统的实现

下图是一个由用户 X 请求发起的,穿过多个服务的分布式系统,A、B、C、D、E 表示不同的子系统或处理过程。

在这个图中, A 是前端,B、C 是中间层、D、E 是 C 的后端。这些子系统通过 rpc 协议连接,例如 gRPC。

一个简单实用的分布式链路追踪系统的实现,就是对服务器上每一次请求以及响应收集跟踪标识符(message identifiers)和时间戳(timestamped events)。

分布式服务的跟踪系统需要记录在一次特定的请求后系统中完成的所有工作的信息。用户请求可以是并行的,同一时间可能有大量的动作要处理,一个请求也会经过系统中的多个服务,系统中时时刻刻都在产生各种跟踪信息,必须将一个请求在不同服务中产生的追踪信息关联起来。

为了将所有记录条目与一个给定的发起者X关联上并记录所有信息,现在有两种解决方案,黑盒(black-box)和基于标注(annotation-based)的监控方案。

黑盒方案:

假定需要跟踪的除了上述信息之外没有额外的信息,这样使用统计回归技术来推断两者之间的关系。

基于标注的方案:

依赖于应用程序或中间件明确地标记一个全局ID,从而连接每一条记录和发起者的请求。

优缺点:

虽然黑盒方案比标注方案更轻便,他们需要更多的数据,以获得足够的精度,因为他们依赖于统计推论。基于标注的方案最主要的缺点是,很明显,需要代码植入。在我们的生产环境中,因为所有的应用程序都使用相同的线程模型,控制流和 RPC 系统,我们发现,可以把代码植入限制在一个很小的通用组件库中,从而实现了监测系统的应用对开发人员是有效地透明。

Dapper 基于标注的方案,接下来我们将介绍 Dapper 中的一些概念知识。

跟踪树和 span

从形式上看,Dapper 跟踪模型使用的是树形结构,Span 以及 Annotation。

在前面的图片中,我们可以看到,整个请求网络是一个树形结构,用户请求是树的根节点。在 Dapper 的跟踪树结构中,树节点是整个架构的基本单元。

span 称为跨度,一个节点在收到请求以及完成请求的过程是一个 span,span 记录了在这个过程中产生的各种信息。每个节点处理每个请求时都会生成一个独一无二的的 span id,当 A -> C -> D 时,多个连续的 span 会产生父子关系,那么一个 span 除了保存自己的 span id,也需要关联父、子 span id。生成 span id 必须是高性能的,并且能够明确表示时间顺序,这点在后面介绍 Jaeger 时会介绍。

Annotation 译为注释,在一个 span 中,可以为 span 添加更多的跟踪细节,这些额外的信息可以帮助我们监控系统的行为或者帮助调试问题。Annotation 可以添加任意内容。

到此为止,简单介绍了一些分布式追踪以及 Dapper 的知识,但是这些不足以严谨的说明分布式追踪的知识和概念,建议读者有空时阅读 Dapper 论文。

要实现 Dapper,还需要代码埋点、采样、跟踪收集等,这里就不再细谈了,后面会介绍到,读者也可以看看论文。

Jaeger 和 OpenTracing

OpenTracing

OpenTracing 是与分布式系统无关的API和用于分布式跟踪的工具,它不仅提供了统一标准的 API,还致力于各种工具,帮助开发者或服务提供者开发程序。

OpenTracing 为标准 API 提供了接入 SDK,支持这些语言:Go, JavaScript, Java, Python, Ruby, PHP, Objective-C, C++, C#。

当然,我们也可以自行根据通讯协议,自己封装 SDK。

接下来我们要一点点弄清楚 OpenTracing 中的一些概念和知识点。由于 jaeger 是 OpenTracing 最好的实现,因此后面讲 Jaeger 就是 Opentracing ,不需要将两者严格区分。

Jaeger 结构

首先是 JAEGER 部分,这部分是代码埋点等流程,在分布式系统中处理,当一个跟踪完成后,通过 jaeger-agent 将数据推送到 jaeger-collector。jaeger-collector 负责处理四面八方推送来的跟踪信息,然后存储到后端,可以存储到 ES、数据库等。Jaeger-UI 可以将让用户在界面上看到这些被分析出来的跟踪信息。

OpenTracing API 被封装成编程语言的 SDK(jaeger-client),例如在 C# 中是 .dll ,Java 是 .jar,应用程序代码通过调用 API 实现代码埋点。

jaeger-Agent 是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。

OpenTracing 数据模型

在 OpenTracing 中,跟踪信息被分为 Trace、Span 两个核心,它们按照一定的结构存储跟踪信息,所以它们是 OpenTracing 中数据模型的核心。

Trace 是一次完整的跟踪,Trace 由多个 Span 组成。下图是一个 Trace 示例,由 8 个 Span 组成。

Tracing:

a Trace can be thought of as a directed acyclic graph (DAG) of Spans。

有点难翻译,大概意思是 Trace 是多个 Span 组成的有向非循环图。

在上面的示例中,一个 Trace 经过了 8 个服务,A -> C -> F -> G 是有严格顺序的,但是从时间上来看,B 、C 是可以并行的。为了准确表示这些 Span 在时间上的关系,我们可以用下图表示:

有个要注意的地方, 并不是 A -> C -> F 表示 A 执行结束,然后 C 开始执行,而是 A 执行过程中,依赖 C,而 C 依赖 F。因此,当 A 依赖 C 的过程完成后,最终回到 A 继续执行。所以上图中 A 的跨度最大。

Span 格式

要深入学习,就必须先了解 Span,请读者认真对照下面的图片和 Json:

json 地址:https://github.com/whuanle/DistributedTracing/issues/1

后续将围绕这张图片和 Json 来举例讲述 Span 相关知识。

Trace

一个简化的 Trace 如下:

注:不同编程语言的字段名称有所差异,gRPC 和 Restful API 的格式也有所差异。

            "traceID": "790e003e22209ca4",
            "spans":[...],
            "processes":{...}

前面说到,在 OpenTracing 中,Trace 是一个有向非循环图,那么 Trace 必定有且只有一个起点。

这个起点会创建一个 Trace 对象,这个对象一开始初始化了 trace id 和 process,trace id 是一个 32 个长度的字符串组成,它是一个时间戳,而 process 是起点进程所在主机的信息。

下面笔者来说一些一下 trace id 是怎么生成的。trace id 是 32个字符串组成,而实际上只使用了 16 个,因此,下面请以 16 个字符长度去理解这个过程。

首先获取当前时间戳,例如获得 1611467737781059 共 16 个数字,单位是微秒,表示时间 2021-01-24 13:55:37,秒以下的单位这里就不给出了,明白表示时间就行。

在 C# 中,将当前时间转为这种时间戳的代码:

        public static long ToTimestamp(DateTime dateTime)
        {
            DateTime dt1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return (dateTime.Ticks - dt1970.Ticks)/10;
        }

// 结果:1611467737781059

如果我们直接使用 Guid 生成或者 string 存储,都会消耗一些性能和内存,而使用 long,刚刚好可以表示时间戳,还可以节约内存。

获得这个时间戳后,要传输到 Jaeger Collector,要转为 byet 数据,为什么要这样不太清楚,按照要求传输就是了。

将 long 转为一个 byte 数组:

            var bytes = BitConverter.GetBytes(time);
			// 大小端
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }

long 占 8 个字节,每个 byte 值如下:

0x00 0x05 0xb9 0x9f 0x12 0x13 0xd3 0x43

然后传输到 Jaeger Collector 中,那么获得的是一串二进制,怎么表示为字符串的 trace id?

可以先还原成 long,然后将 long 输出为 16 进制的字符串:

转为字符串(这是C#):

Console.WriteLine(time.ToString("x016"));

结果:

0005b99f1213d343

Span id 也是这样转的,每个 id 因为与时间戳相关,所以在时间上是唯一的,生成的字符串也是唯一的。

这就是 trace 中的 trace id 了,而 trace process 是发起请求的机器的信息,用 Key-Value 的形式存储信息,其格式如下:

                        {
                            "key": "hostname",
                            "type": "string",
                            "value": "Your-PC"
                        },
                        {
                            "key": "ip",
                            "type": "string",
                            "value": "172.6.6.6"
                        },
                        {
                            "key": "jaeger.version",
                            "type": "string",
                            "value": "CSharp-0.4.2.0"
                        }

Ttace 中的 trace id 和 process 这里说完了,接下来说 trace 的 span。

Span

Span 由以下信息组成:

  • An operation name:操作名称,必有;
  • A start timestamp:开始时间戳,必有;
  • A finish timestamp:结束时间戳,必有;
  • Span Tags.:Key-Value 形式表示请求的标签,可选;
  • Span Logs:Key-Value 形式表示,记录简单的、结构化的日志,必须是字符串类型,可选;
  • SpanContext :跨度上下文,在不同的 span 中传递,建立关系;
  • Referencest:引用的其它 Span;

span 之间如果是父子关系,则可以使用 SpanContext 绑定这种关系。父子关系有 ChildOfFollowsFrom 两种表示,ChildOf 表示 父 Span 在一定程度上依赖子 Span,而 FollowsFrom 表示父 Span 完全不依赖其子Span 的结果。

一个 Span 的简化信息如下(不用理会字段名称大小写):

{
                    "traceID": "790e003e22209ca4",
                    "spanID": "4b73f8e8e77fe9dc",
                    "flags": 1,
                    "operationName": "print-hello",
                    "references": [],
                    "startTime": 1611318628515966,
                    "duration": 259,
                    "tags": [
                        {
                            "key": "internal.span.format",
                            "type": "string",
                            "value": "proto"
                        }
                    ],
                    "logs": [
                        {
                            "timestamp": 1611318628516206,
                            "fields": [
                                {
                                    "key": "event",
                                    "type": "string",
                                    "value": "WriteLine"
                                }
                            ]
                        }
                    ]
}

OpenTracing API

在 OpenTracing API 中,有三个主要对象:

  • Tracer
  • Span
  • SpanContext

Tracer可以创建Spans并了解如何跨流程边界对它们的元数据进行Inject(序列化)和Extract(反序列化)。它具有以下功能:

  • 开始一个新的 Span
  • Inject一个SpanContext到一个载体
  • Extract一个SpanContext从载体

由起点进程创建一个 Tracer,然后启动进程发起请求,每个动作产生一个 Span,如果有父子关系,Tracer 可以将它们关联起来。当请求完成后, Tracer 将跟踪信息推送到 Jaeger-Collector中。

SpanContext 是在不同的 Span 中传递信息的,SpanContext 包含了简单的 Trace id、Span id 等信息。

我们继续以下图作为示例讲解。

A 创建一个 Tracer,然后创建一个 Span,代表自己 (A),再创建两个 Span,分别代表 B、C,然后通过 SpanContext 传递一些信息到 B、C;B 和 C 收到 A 的消息后,也创建一个 Tracer ,用来 Tracer.extract(...) ;其中 B 没有后续,可以直接返回结果;而 C 的 Tracer 继续创建两个 Span,往 D、E 传递 SpanContext。

这个过程比较复杂,笔者讲不好,建议读者参与 OpenTracing 的官方文档。

详细的 OpenTracing API,可以通过编程语言编写相应服务时,去学习各种 API 的使用。

到此这篇关于.NET Core分布式链路追踪框架的基本实现原理的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 从零开始使用Dapr简化微服务的示例

    目录 序言 1.安装Docker 2.安装Dapr CLI 3.安装Net6 SDK 4.创建应用程序 4.1.服务调用 4.2.发布订阅 4.3.状态管理 4.4.链路追踪  5.总结 序言 现有的微服务模式需要再业务代码中集成大量基础设施模块,比如注册中心,服务发现,服务调用链路追踪,请求熔断,重试限流等等,使得系统过于臃肿重量级. Dapr作为新一代微服务模式,使用sidecar模式,简化了现有微服务系统代码,将基础设施层以sidecar模式分离,使得开发人员更集中于业务逻辑编写. 本文以

  • .NET Core通过dotnet publish命令发布应用

    一.前言 .NET Core 3.0中新增加了一个特性:Publishing Single EXEs,可以通过dotnet publish 命令将整个.net core应用发布为一个可执行文件. 二.准备工作 使用VS 2019新创建一个MVC项目: 然后使用dotnet publish命令发布: dotnet publish 如下图所示: 我们查看发布后的文件 可以看到发布之后有很多文件.接下来我们发布成单个可执行文件. 1.Windows上的发布命令 在Windows系统上面执行如下的发布命

  • .NET MAUI项目中创建超链接

    .NET MAUI Preview 13预览版中,.NET MAUI 支持带标签控件的格式化文本. 标签中的格式化文本 标签是显示带或不带文本环绕的文本的视图.使用格式化文本功能(现在位于单个标签中),您可以使用不同的 span 元素为每个设置选择多个选项.例如,您可以对单个标签中的单词应用单独的颜色.这将使标签更具装饰性.Span 元素支持以下选项: CharacterSpacing FontAttributes FontFamily FontSize TextColor TextTransf

  • .Net结合JS实现URL编码与解码

    目录 解决问题 1.为什么需要编码? 1.1 浏览器对于中文的编码 1.2 需要编码的原因还有几点 2.怎样编码? 3.实际出现的问题解决方法 3.1.escape函数: 3.2.encodeURI函数 3.3.encodeURIComponent函数 在项目中碰到了ajax传来的参数,后台接收值乱码(如下图)的问题 在此记录一下 前台: 后台: 解决问题 为什么需要编码 怎样编码 实际出现的问题解决方法 1.为什么需要编码? URL 只能使用 ASCII 字符集来通过因特网进行发送.也就是说U

  • .NET项目在k8s中运行的Dapr持续集成流程

    目录 流程选择 项目结构 dapr.yaml Dockerfile Pipelines 持续集成的配置文件 Release 发布流程配置文件 其它流程 注:本文中主要讨论 .NET6.0项目在 k8s 中运行的 Dapr 的持续集成流程, 但实际上不是Dapr的项目部署到K8s也是相同流程,只是k8s的yaml配置文件有所不同 流程选择 基于 Dapr 的项目持续集成包含以下流程 编译并打包项目 构建 Dockerfile,并推送镜像push image至私有仓库 准备 k8s 部署的配置文件

  • Ansible部署K8s集群的方法

    目录 检查网络:k8s-check.yaml检查k8s各主机的网络是否可达; 检查k8s各主机操作系统版本是否达到要求: 配置k8s集群dns解析:k8s-hosts-cfg.yaml 配置yum源:k8s-yum-cfg.yaml 时钟同步:k8s-time-sync.yaml 禁用iptable.firewalld.NetworkManager服务 禁用SElinux.swap:k8s-SE-swap-disable.yaml 修改内核:k8s-kernel-cfg.yaml 配置ipvs:

  • .NET Core分布式链路追踪框架的基本实现原理

    分布式追踪 什么是分布式追踪 分布式系统 当我们使用 Google 或者 百度搜索时,查询服务会将关键字分发到多台查询服务器,每台服务器在自己的索引范围内进行搜索,搜索引擎可以在短时间内获得大量准确的搜索结果:同时,根据关键字,广告子系统会推送合适的相关广告,还会从竞价排名子系统获得网站权重.通常一个搜索可能需要成千上万台服务器参与,需要经过许多不同的系统提供服务. 多台计算机通过网络组成了一个庞大的系统,这个系统即是分布式系统. 在微服务或者云原生开发中,一般认为分布式系统是通过各种中间件/服

  • .NET Core分布式链路追踪框架的基本实现原理

    目录 分布式追踪 什么是分布式追踪 分布式系统 分布式追踪 分布式追踪有什么用呢 Dapper 分布式追踪系统的实现 跟踪树和 span Jaeger 和 OpenTracing OpenTracing Jaeger 结构 OpenTracing 数据模型 Span 格式 Trace Span OpenTracing API 分布式追踪 什么是分布式追踪 分布式系统 当我们使用 Google 或者 百度搜索时,查询服务会将关键字分发到多台查询服务器,每台服务器在自己的索引范围内进行搜索,搜索引擎

  • php链路追踪框架skywalking介绍

    目录 Skywalking介绍 Skywalking功能介绍 Skywalking介绍 Skywalking是一个国产的开源框架,2015年有吴晟个人开源,2017年加入Apache孵化器,国人开源的产品,主要开发人员来自于华为,2019年4月17日Apache董事会批准SkyWalking成为顶级项目,支持Java..Net.NodeJs等探针,数据存储支持Mysql.Elasticsearch等,跟Pinpoint一样采用字节码注入的方式实现代码的无侵入,探针采集数据粒度粗,但性能表现优秀,

  • Go 分布式链路追踪实现原理解析

    目录 为什么需要分布式链路追踪系统 微服务架构给运维.排障带来新挑战 分布式链路追踪系统如何帮助我们 分布式链路追踪系统架构概览 核心概念 一般架构 协议标准和开源实现 应用侧调用链跟踪实现方案概览 应用侧核心任务 基于 OTEL 库实现调用拦截 HttpServer Handler 生成 Span 过程 HttpClient 请求生成 Span 过程 基于 OTEL 库实现调用链跟踪总结 非侵入调用链跟踪实现思路 Go 非侵入链路追踪实现原理 在分布式.微服务架构下,应用一个请求往往贯穿多个分

  • spring cloud 分布式链路追踪的方法

    一篇讲了微服务之间的调用spring cloud eureka 微服务之间的调用 微服务之间进行调用 那么如果我负责一个模块 别人负责另一个模块 我调用了他的方法 测试那边却报了错 那是我的问题还是他的问题 这个时候大家应该就能想到日志可以解决这个问题 如何使用日志呢 先在配置文件中加 logging: path: D:\logs\poppy-mall #日志的存放地址 最好再加个项目名的文件夹 可以更容易的区分 level: org.poppy.mall: info #日志的级别 org.po

  • net core下链路追踪skywalking安装和简单使用教程

    当我们用很多服务时,各个服务间的调用关系是怎么样的?各个服务单调用的顺序\时间性能怎么样?服务出错了,到底是哪个服务引起的?这些问题我们用什么方案解决呢,以前的方式是各个系统自己单独做日志,出了问题从暴出问题的服务开始一个一个服务的排查,耗时耗力,有些日志不全的,还不一定查得出来.好在现在有Skywalking链路追踪系统,可以不用写任何代码,就追踪到各个服务间的调用关系和性能状态等. 本文将从0开始搭建两个webapi项目,使用Skywalking来追踪他们之间的调用关系及响应时间.开发环境为

  • ASP.Net Core中的日志与分布式链路追踪

    目录 .NET Core 中的日志 控制台输出 非侵入式日志 Microsoft.Extensions.Logging ILoggerFactory ILoggerProvider ILogger Logging Providers 怎么使用 日志等级 Trace.Debug 链路跟踪 OpenTracing 上下文和跟踪功能 跟踪单个功能 将多个跨度合并到一条轨迹中 传播过程中的上下文 分布式链路跟踪 在不同进程中跟踪 在 ASP.NET Core 中跟踪 OpenTracing API 和

  • Jaeger Client Go入门并实现链路追踪

    目录 Jaeger 部署 Jaeger 从示例了解 Jaeger Client Go 了解 trace.span tracer 配置 Sampler 配置 Reporter 配置 分布式系统与span 怎么调.怎么传 HTTP,跨进程追踪 客户端 Web 服务端 Tag . Log 和 Ref Jaeger OpenTracing 是开放式分布式追踪规范,OpenTracing API 是一致,可表达,与供应商无关的API,用于分布式跟踪和上下文传播. OpenTracing 的客户端库以及规范

  • Jaeger Client Go入门并实现链路追踪

    目录 Jaeger 部署 Jaeger 从示例了解 Jaeger Client Go 了解 trace.span tracer 配置 Sampler 配置 Reporter 配置 分布式系统与span 怎么调.怎么传 HTTP,跨进程追踪 客户端 Web 服务端 Tag . Log 和 Ref Jaeger OpenTracing 是开放式分布式追踪规范,OpenTracing API 是一致,可表达,与供应商无关的API,用于分布式跟踪和上下文传播. OpenTracing 的客户端库以及规范

  • .NET core项目AsyncLocal在链路追踪中的应用

    目录 前言 老传统做法 AspNetCore的TraceIdentifier AsyncLocal在链路追踪的应用 定义 示例 项目应用 AspNet4 AspNetCore 前言 在项目生产中日志的记录是必不可少的,在.net项目中,要说日志组件,log4net绝对可有一席之地,随着公司业务的发展,微服务则必定无可避免.在跨服务中通过日志进行分析性能或者排查故障点,如何快速定位日志尤为关键.链路追踪技术的出现正是解决这些痛点的. 分布式链路追踪需要收集单次请求所经过的所有服务,而且为了知道请求

随机推荐