一、简介

在本教程中,我们将讨论可观察性以及为什么它在分布式系统中发挥重要作用。我们将介绍构成可观察性的数据类型。这将帮助我们了解从分布式系统收集、存储和分析遥测数据的挑战。

最后,我们将介绍可观测性领域的一些行业标准和流行工具。

2. 什么是可观察性?

让我们切入正题,首先给出正式的定义!可观察性是 指仅通过系统的外部输出来测量系统内部状态的能力

对于像微服务这样的分布式系统,这些外部输出基本上被称为遥测数据。它包括计算机的资源消耗、计算机上运行的应用程序生成的日志等信息。

2.1.遥测数据的类型

我们可以将遥测数据分为三类,我们称之为 可观察性的三大支柱: 日志、指标和跟踪。让我们更详细地了解它们。

日志是应用程序在执行代码期间在离散点生成的文本行 。通常,这些都是结构化的,并且通常以不同的严重级别生成。这些很容易生成,但通常会带来性能成本。此外,我们可能需要像 Logstash 这样的额外工具来有效地收集、存储和分析日志。

简而言之, 指标是表示为我们在一段时间内计算或聚合的计数或度量的值 。这些值表示有关虚拟机等系统的一些数据,例如虚拟机每秒的内存消耗量。这些可以来自主机、应用程序和云平台等各种来源。

跟踪对于单个请求可以流经多个应用程序的分布式系统非常重要。 跟踪是请求流经分布式系统时分布式事件的表示 。这些对于定位分布式系统中的瓶颈、缺陷或其他问题非常有帮助。

2.2.可观察性的好处

首先,我们需要理解为什么我们需要系统的可观察性。我们大多数人可能都面临着对生产系统上难以理解的行为进行故障排除的挑战。不难理解,我们破坏生产环境的选择是有限的。这几乎让我们可以分析系统生成的数据。

可观察性 对于调查系统开始偏离其预期状态的情况非常有价值 。完全防止这些情况也非常有用!根据系统生成的可观察数据仔细设置警报可以帮助我们在系统完全故障之前采取补救措施。此外,这些数据为我们提供了重要的分析见解,以调整系统以获得更好的体验。

可观察性的需求虽然对于任何系统都很重要,但对于分布式系统来说也相当重要。此外,我们的系统可以跨越公共云和私有云以及本地环境。此外,它的规模和复杂性随着时间的推移而不断变化。这通常会带来以前从未预料到的问题。高度可观察的系统可以极大地帮助我们处理这种情况。

3. 可观察性与监控

DevOps 实践中,我们经常听到与可观察性相关的监控。那么,这些术语有什么区别呢?嗯,它们都有相似的功能,使我们能够保持系统的可靠性。但它们之间有微妙的区别,事实上,它们之间也存在联系。只有可观察的系统,我们才能有效地对其进行监控!

监控基本上是指 通过一组预定义的指标和日志来观察系统状态的做法 。这本质上意味着我们正在监视一组已知的故障。然而,在分布式系统中,有很多动态变化不断发生。这会导致我们从未发现的问题。因此,我们的监控系统可能会错过它们。

另一方面,可观察性帮助我们了解系统的内部状态。这可以使我们能够询问有关系统行为的任意问题。例如,我们可以提出复杂的问题,例如每个服务在出现问题时如何处理请求。随着时间的推移,它可以帮助建立有关系统动态行为的知识。

要理解为什么会这样,我们需要理解基数的概念。 基数是指集合中唯一项的数量 。例如,用户的社会安全号码集的基数将高于性别。为了回答有关系统行为的任意问题,我们需要高基数数据。然而,监视通常只处理低基数数据。

4. 分布式系统中的可观察性

正如我们之前所看到的,可观察性对于复杂的分布式系统特别有用。但是,到底是什么让分布式系统变得复杂,这种系统中的可观察性面临哪些挑战?理解这个问题对于理解过去几年围绕这个主题发展的工具和平台生态系统非常重要。

在分布式系统中, 有许多移动组件会动态地改变系统景观 。此外,动态可扩展性意味着在任何时间点,服务运行的实例数量都是不确定的。这使得收集、整理和存储日志和指标等系统输出的工作变得困难:

此外,仅了解系统应用程序内发生的情况是不够的。例如,问题可能出在网络层或负载均衡器上。然后还有数据库、消息传递平台,这样的例子不胜枚举。 重要的是所有这些组件在任何时候都是可观察的 。我们必须能够从系统的各个部分收集和集中有意义的数据。

此外,由于多个组件同步或异步地一起工作,因此很难查明异常的根源。例如,很难说系统中的哪个服务导致瓶颈随着性能下降而升级。正如我们之前所见,跟踪对于调查此类问题非常有用。

5. 可观察性的演变

可观测性起源于 控制理论,控制理论是应用数学的一个分支,研究利用反馈来影响系统的行为以实现预期目标 。我们可以将这一原则应用于多个行业,从工厂到飞机运营。对于软件系统来说,自从Twitter 等一些社交网站开始大规模运行以来,这已经变得流行起来。

直到最近几年,大多数软件系统都是单一的,因此在发生事件时很容易对其进行推理。监控在指示典型故障场景方面非常有效。此外,调试代码以识别问题也很直观。但是,随着微服务架构和云计算的出现,这很快就成为一项艰巨的任务。

随着这种演变的继续,软件系统不再是静态的——它们有许多动态变化的组件。这导致了以前从未预料到的问题。这 催生了应用程序性能管理 (APM) 下的许多工具 ,例如AppDynamicsDynatrace 。这些工具有望提供更好的方式来理解应用程序代码和系统行为。

尽管这些工具已经取得了长足的发展,但当时它们还是相当基于指标的。这使得他们无法提供我们所需的有关系统状态的视角。然而,他们向前迈出了一大步。今天,我们拥有了解决可观察性三大支柱问题的工具组合。当然,底层组件也需要是可观察的!

6. 实践可观察性

现在我们已经介绍了足够的可观察性理论,让我们看看如何将其付诸实践。我们将使用一个简单的基于微服务的分布式系统,我们将在其中使用Java 中的 Spring Boot开发各个服务。这些服务将使用 REST API 相互同步通信。

我们来看看我们的系统服务:

这是一个相当简单的分布式系统,其中 数学服务 使用由 加法服务乘法服务 等提供的 API。此外, 数学服务 还公开 API 来计算各种公式。我们将跳过创建这些微服务的细节,因为它非常简单。

这项练习的重点是认识当今可观察性背景下最常见的标准和流行的工具。我们的具有可观察性的系统的目标架构如下图所示:

其中许多还处于云原生计算基金会(CNCF)认可的不同阶段,该基金会是一个促进容器技术进步的组织。我们将看到如何在我们的分布式系统中使用其中一些。

7. 使用 OpenTracing 进行跟踪

我们已经看到跟踪如何提供宝贵的见解来了解单个请求如何通过分布式系统传播。 OpenTracing是CNCF旗下的一个孵化项目。它 为分布式跟踪提供供应商中立的 API 和工具 。这有助于我们向不特定于任何供应商的代码添加检测。

符合 OpenTracing 的可用跟踪器列表正在快速增长。 Jaeger是最受欢迎的跟踪器之一,它也是 CNCF 下的一个毕业项目。

让我们看看如何在我们的应用程序中将 Jaeger 与 OpenTracing 结合使用:

我们稍后会详细介绍。请注意,还有其他几个选项,例如LightStepInstanaSkyWalkingDatadog 。我们可以轻松地在这些跟踪器之间切换,而无需更改在代码中添加检测的方式。

7.1.概念和术语

OpenTracing 中的跟踪由跨度组成。 跨度是分布式系统中完成的单个工作单元 。基本上,轨迹可以看作是跨度的有向无环图 (DAG)。我们将跨度之间的边称为参考。分布式系统中的每个组件都会向跟踪添加一个跨度。 Span 包含对其他 Span 的引用,这有助于跟踪重新创建请求的生命周期。

我们可以使用时间轴或图表来可视化迹线中跨度之间的因果关系:

在这里,我们可以看到 OpenTracing 定义的两种类型的引用 ,“ChildOf”和“FollowsFrom”。这些建立了子跨度和父跨度之间的关系。

OpenTracing 规范定义了跨度捕获的状态:

  • 操作名称
  • 开始时间戳和结束时间戳
  • 一组键值跨度标签
  • 一组键值跨度日志
  • SpanContext

标签允许用户定义的注释成为我们用来查询和过滤跟踪数据的范围的一部分 。 Span 标签适用于整个范围。同样,日志允许跨度捕获日志消息以及来自应用程序的其他调试或信息输出。跨度日志可以应用于跨度内的特定时刻或事件。

最后, SpanContext 将跨度联系在一起 。它跨越流程边界传送数据。让我们快速浏览一下典型的 SpanContext:


正如我们所看到的,它主要包括:

  • 依赖于实现的状态,如 spanIdtraceId
  • 任何行李物品,它们是跨越流程边界的键值对

7.2.设置和仪器草案

我们将从安装Jaeger开始,这是我们将使用的与 OpenTracing 兼容的跟踪器。尽管它有多个组件,但我们可以使用一个简单的 Docker 命令来安装它们:

docker run -d -p 5775:5775/udp -p 16686:16686 jaegertracing/all-in-one:latest

接下来,我们需要在应用程序中导入必要的依赖项。对于基于 Maven 的应用程序,这就像添加依赖项一样简单:

<dependency>
    <groupId>io.opentracing.contrib</groupId>
    <artifactId>opentracing-spring-jaeger-web-starter</artifactId>
    <version>3.3.1</version>
</dependency>

对于基于 Spring Boot 的应用程序,我们可以利用第三方提供的这个库。这包括所有必要的依赖项,并提供必要的默认配置来检测 Web 请求/响应并向 Jaeger 发送跟踪。

在应用程序方面,我们需要创建一个 Tracer

@Bean
public Tracer getTracer() {
    Configuration.SamplerConfiguration samplerConfig = Configuration
      .SamplerConfiguration.fromEnv()
      .withType("const").withParam(1);
    Configuration.ReporterConfiguration reporterConfig = Configuration
      .ReporterConfiguration.fromEnv()
      .withLogSpans(true);
    Configuration config = new Configuration("math-service")
      .withSampler(samplerConfig)
      .withReporter(reporterConfig);
    return config.getTracer();
}

这足以生成请求所经过的服务的跨度。如有必要,我们还可以在我们的服务中生成子跨度:

Span span = tracer.buildSpan("my-span").start();
// Some code for which which the span needs to be reported
span.finish();

这是非常简单和直观的,但当我们分析复杂的分布式系统时,它非常强大。

7.3.痕量分析

Jaeger 附带一个默认可通过端口 16686 访问的用户界面。 它提供了一种通过可视化查询、过滤和分析跟踪数据的简单方法。让我们看一下分布式系统的示例跟踪:

正如我们所看到的,这是由其 TraceId 标识的一个特定跟踪的可视化。 它清楚地显示了此跟踪中的所有跨度,以及它所属的服务以及完成所需的时间等详细信息。这可以帮助我们了解在非典型行为的情况下问题可能出在哪里。

8. OpenCensus 的指标

OpenCensus提供了 各种语言的库,使我们能够从应用程序中收集指标和分布式跟踪 。它起源于 Google,但此后已由不断壮大的社区开发为开源项目。 OpenCensus 的好处是它可以将数据发送到任何后端进行分析。这使我们能够抽象我们的检测代码,而不是将其耦合到特定的后端。

尽管 OpenCensus 可以支持跟踪和指标,但我们仅将其用于示例应用程序中的指标。 我们可以使用几个后端 。最流行的指标工具之一是Prometheus ,它是一个开源监控解决方案,也是 CNCF 下的一个毕业项目。让我们看看 Jaeger 与 OpenCensus 如何与我们的应用程序集成:

虽然 Prometheus 自带了用户界面,但我们可以使用像Grafana这样与 Prometheus 集成良好的可视化工具。

8.1.概念和术语

在 OpenCensus 中, 度量表示要记录的度量类型 。例如,请求有效负载的大小可以是要收集的一种度量。 测量是通过测量记录数量后产生的数据点 。例如,80 kb 可以是请求有效负载大小测量的测量值。所有度量均通过名称、描述和单位进行标识。

为了分析统计数据,我们需要使用视图聚合数据。 视图基本上是应用于度量和标签(可选)的聚合的耦合 。 OpenCensus 支持计数、分布、总和和最后值等聚合方法。视图由名称、描述、度量、标签键和聚合组成。多个视图可以使用具有不同聚合的相同度量。

标签是与记录的测量相关的键值对数据, 用于提供上下文信息并在分析过程中区分和分组指标。当我们聚合测量值以创建指标时,我们可以使用标签作为标签来分解指标。标签还可以作为分布式系统中的请求标头进行传播。

最后, 导出器可以将指标发送到任何能够使用它们的后端 。导出器可以根据后端进行更改,而不会对客户端代码产生任何影响。这使得 OpenCensus 在指标收集方面与供应商无关。对于大多数流行的后端(例如 Prometheus),有很多支持多种语言的导出器。

8.2.设置和仪器

由于我们将使用 Prometheus 作为后端,因此我们应该首先安装它。使用官方 Docker 镜像,这既快速又简单。 Prometheus 通过抓取这些目标上的指标端点来从受监控的目标收集指标。因此,我们需要在 Prometheus 配置 YAML 文件 prometheus.yml 中提供详细信息:

scrape_configs:
  - job_name: 'spring_opencensus'
    scrape_interval: 10s
    static_configs:
      - targets: ['localhost:8887', 'localhost:8888', 'localhost:8889']

这是一个基本配置,告诉 Prometheus 从哪些目标中抓取指标。现在, 我们可以用一个简单的命令启动 Prometheus

docker run -d -p 9090:9090 -v \
  ./prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

为了定义自定义指标,我们首先 定义一个度量

MeasureDouble M_LATENCY_MS = MeasureDouble
  .create("math-service/latency", "The latency in milliseconds", "ms");

接下来,我们需要 记录我们刚刚定义的测量值

StatsRecorder STATS_RECORDER = Stats.getStatsRecorder();
STATS_RECORDER.newMeasureMap()
  .put(M_LATENCY_MS, 17.0)
  .record();

然后,我们需要 为我们的度量定义一个聚合和视图,这将使我们能够将其导出为度量

Aggregation latencyDistribution = Distribution.create(BucketBoundaries.create(
  Arrays.asList(0.0, 25.0, 100.0, 200.0, 400.0, 800.0, 10000.0)));
View view = View.create(
  Name.create("math-service/latency"),
  "The distribution of the latencies",
  M_LATENCY_MS,
  latencyDistribution,
  Collections.singletonList(KEY_METHOD)),
};
ViewManager manager = Stats.getViewManager();
manager.registerView(view);

最后,为了将视图导出到 Prometheus,我们需要创建并注册收集器并将 HTTP 服务器作为守护进程运行:

PrometheusStatsCollector.createAndRegister();
HTTPServer server = new HTTPServer("localhost", 8887, true);

这是一个简单的示例,说明了我们如何记录延迟作为应用程序的度量,并将其作为视图导出到 Prometheus 进行存储和分析。

8.3.指标分析

OpenCensus 提供称为 zPages 的进程内网页 ,用于显示从附加到的进程中收集的数据。此外,Prometheus 提供了表达式浏览器,允许我们输入任何表达式并查看其结果。然而,像Grafana这样的工具提供了更优雅、更高效的可视化。

使用官方 Docker 镜像安装 Grafana 非常简单:

docker run -d --name=grafana -p 3000:3000 grafana/grafana

Grafana 支持查询 Prometheus - 我们只需在 Grafana 中添加 Prometheus 作为数据源即可。然后,我们可以使用常规 Prometheus 指标查询表达式创建一个图表:

我们可以使用多种图形设置来调整图形。此外,还有几个可用于 Prometheus 的预构建 Grafana 仪表板,我们可能会发现它们很有用。

9. 使用 Elastic Stack 记录日志

日志可以提供有关应用程序对事件的反应方式的宝贵见解。不幸的是,在分布式系统中,它被分成多个组件。因此,从所有组件收集日志并将其存储在一个位置以进行有效分析变得很重要。此外,我们需要一个直观的用户界面来有效地查询、过滤和引用日志。

Elastic Stack 基本上是一个日志管理平台,直到最近,它还是三个产品的集合 :Elasticsearch、Logstash 和 Kibana (ELK)。

不过,从那时起, Beats 就被添加到这个堆栈中,以实现高效的数据收集

让我们看看如何在我们的应用程序中使用这些产品:

正如我们所看到的,在 Java 中,我们可以使用SLF4J 这样的简单抽象Logback这样的记录器来生成日志。我们将在这里跳过这些细节。

Elastic Stack 产品是开源的,由Elastic维护。这些共同为分布式系统中的日志分析提供了一个引人注目的平台。

9.1.概念和术语

正如我们所看到的,Elastic Stack 是多个产品的集合。这些产品中最早的是Elasticseach ,它是 一个分布式、RESTful、基于 JSON 的搜索引擎 。由于其灵活性和可扩展性,它非常受欢迎。该产品奠定了 Elastic 的基础。它基本上基于 Apache Lucene 搜索引擎。

Elasticsearch 将索引存储为文档,文档是存储的基本单位 。这些是简单的 JSON 对象。我们可以使用类型来细分文档内相似类型的数据。索引是文档的逻辑分区。通常,我们可以将索引水平分割成分片以实现可扩展性。此外,我们还可以复制分片以实现容错:

Logstash一个日志聚合器,它从各种输入源收集数据, 它还执行不同的转换和增强,并将其发送到输出目的地。由于 Logstash 占用空间较大,因此我们有Beats ,它们是轻量级数据传送器,我们可以将其作为代理安装在服务器上。最后, Kibana一个在 Elasticsearch 之上工作的可视化层

这些产品共同提供了一个完整的套件来执行日志数据的聚合、处理、存储和分析:

借助这些产品,我们可以为日志数据创建生产级数据管道。但是,扩展此架构以处理大量日志数据是完全可能的,并且在某些情况下也是必要的。我们可以在 Logstash 前面放置一个像Kafka这样的缓冲区,以防止下游组件淹没它。 Elastic Stack 在这方面非常灵活。

9.2.设置和仪器

正如我们之前所见,Elastic Stack 包含多种产品。当然,我们可以独立安装它们。然而,这很耗时。幸运的是,Elastic 提供了官方 Docker 镜像,让这一切变得简单。

启动单节点 Elasticsearch 集群就像运行 Docker 命令一样简单:

docker run -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  docker.elastic.co/elasticsearch/elasticsearch:7.13.0

同样,安装 Kibana 并将其连接到 Elasticsearch 集群也非常简单:

docker run -p 5601:5601 \
  -e "ELASTICSEARCH_HOSTS=http://localhost:9200" \
  docker.elastic.co/kibana/kibana:7.13.0

安装和配置 Logstash 稍微复杂一些,因为我们必须提供数据处理所需的设置和管道。实现此目的的一种更简单的方法是在官方映像之上创建自定义映像:

FROM docker.elastic.co/logstash/logstash:7.13.0
RUN rm -f /usr/share/logstash/pipeline/logstash.conf
ADD pipeline/ /usr/share/logstash/pipeline/
ADD config/ /usr/share/logstash/config/

让我们看一下与 Elasticsearch 和 Beats 集成的 Logstash 配置文件示例:

input {
  tcp {
  port => 4560
  codec => json_lines
  }
  beats {
    host => "127.0.0.1"
    port => "5044"
  }
}
output{
  elasticsearch {
  hosts => ["localhost:9200"]
  index => "app-%{+YYYY.MM.dd}"
  document_type => "%{[@metadata][type]}"
  }
  stdout { codec => rubydebug }
}

根据数据源的不同,有多种类型的 Beats 可用。对于我们的示例,我们将使用 Filebeat。安装和配置 Beats 最好在自定义映像的帮助下完成:

FROM docker.elastic.co/beats/filebeat:7.13.0
COPY filebeat.yml /usr/share/filebeat/filebeat.yml
USER root
RUN chown root:filebeat /usr/share/filebeat/filebeat.yml
USER filebeat

让我们看一下 Spring Boot 应用程序的示例 filebeat.yml

filebeat.inputs:
- type: log
enabled: true
paths:
  - /tmp/math-service.log
output.logstash:
hosts: ["localhost:5044"]

这是对 Elastic Stack 安装和配置的非常粗略但完整的解释。详细介绍超出了本教程的范围。

9.3.日志分析

Kibana 为我们的日志提供了非常直观且强大的可视化工具。我们可以通过默认 URL http://localhost:5601访问 Kibana 界面。我们可以选择可视化并为我们的应用程序创建仪表板。

让我们看一个示例仪表板:

Kibana 提供了相当广泛的功能来查询和过滤日志数据。这些超出了本教程的范围。

10.可观察性的未来

现在,我们已经了解了为什么可观察性是分布式系统的一个关键问题。我们还了解了一些处理不同类型遥测数据的流行选项,这些选项可以使我们实现可观察性。但事实是, 组装所有部件仍然相当复杂且耗时 。我们必须处理很多不同的产品。

该领域的关键进步之一是OpenTelemetry ,它是 CNCF 中的一个沙箱项目。基本上, OpenTelemetry 是通过 OpenTracing 和 OpenCensus 项目的精心合并而形成的 。显然,这是有道理的,因为我们只需要处理跟踪和指标的单一抽象。

更重要的是,OpenTelemetry 计划支持日志并使它们成为分布式系统的完整可观察性框架 。此外,OpenTelemetry 支持多种语言,并与流行的框架和库很好地集成。此外,OpenTelemetry 通过软件桥向后兼容 OpenTracing 和 OpenCensus。

OpenTelemetry 仍在进行中,我们预计它会在未来几天内成熟。同时,为了减轻我们的痛苦, 一些可观察平台结合了前面讨论的许多产品来提供无缝体验 。例如, Logz.io结合了 ELK、Prometheus 和 Jaeger 的强大功能,提供可扩展的平台即服务。

随着带有创新解决方案的新产品进入市场, 可观测性领域正在快速成熟。例如, Micrometer在仪器客户端上为多个监控系统提供了一个供应商中立的外观。最近, OpenMetrics发布了其规范,用于创建大规模传输云原生指标的事实上的标准。

11. 结论

在本教程中,我们了解了可观察性的基础知识及其在分布式系统中的含义。我们还实现了一些当今流行的选项,以在简单的分布式系统中实现可观察性。

这让我们了解了 OpenTracing、OpenCensus 和 ELK 如何帮助我们构建可观察的软件系统。最后,我们讨论了该领域的一些新发展,以及我们如何期望可观测性在未来发展和成熟。