# 工具

此页面提供了一套实用的准则来检测您的代码。

## 如何监测 <a href="#how-to-instrument" id="how-to-instrument"></a>

简短的答案是对所有东西进行监测。每个库，子系统和服务都应至少具有一些数据指标，以使您大致了解其性能。

监测应该是代码的组成部分。在您使用它们的同一文件中实例化数据指标类。当您追踪错误时，这使从告警到控制台再到代码变得容易。

### 三种类型的服务 <a href="#the-three-types-of-services" id="the-three-types-of-services"></a>

为了进行监控，通常可以将服务分为三类：在线服务，离线处理和批处理作业。它们之间有重叠，但是每种服务都非常适合这些类别之一。

#### 在线服务系统 <a href="#online-serving-systems" id="online-serving-systems"></a>

在线服务系统是用户或其他系统期望即时响应的系统。例如，大多数数据库和 HTTP 请求都属于此类。

在这样的系统中，关键指标是执行的查询数，错误和延迟。处理中的请求数也可能有用。

要计算失败的查询，请参阅下面的[失败](#failures)部分。

在线服务系统应该在客户端和服务器端都受到监控。如果双方看到不同的行为，这对于调试非常有用。如果服务有很多客户端，则对于该服务来说单独跟踪它们是不切实际的，因此他们必须依赖自己的统计信息。

在查询开始或结束时对查询进行计数时要保持一致。建议结束时，因为它将与错误和等待时间统计信息保持一致，并且往往更易于编写代码。

#### 离线处理 <a href="#offline-processing" id="offline-processing"></a>

对于离线处理，没有人积极等待响应，并且工作分批很常见。也可能有多个处理阶段。

对于每个阶段，跟踪进来的项目，进行中的项目，上次处理项目的时间以及发出的项目数。如果进行批处理，则还应跟踪进出的批处理。

了解系统上次处理某物的时间对于检测它是否已停止很有用，但这是非常本地化的信息。更好的方法是通过系统发送心跳: 某些虚拟项会一直传递，并包括插入时的时间戳。每个阶段最多可以公开最近看到的心跳时间戳，让您知道物品在系统中传播需要花费多长时间。对于没有静默期且不进行任何处理的系统，可能不需要明确的心跳。

#### 批处理作业 <a href="#batch-jobs" id="batch-jobs"></a>

离线处理和批处理作业之间存在模糊界限，因为离线处理可能在批处理作业中完成。批处理作业的特点是它们不连续运行，这使它们的数据采集变得困难。

批处理作业的关键指标是上一次成功。跟踪作业的每个主要阶段花费了多长时间，整体运行时间以及作业最后一次完成(成功或失败)的时间也很有用。 这些都是数据指标，应该[推送到 PushGateway](https://hulining.gitbook.io/prometheus/v2.19/instrumenting/pushing)。通常，还有一些对于特定作业的总体统计信息跟踪很有用，例如处理的记录总数。

对于要花费几分钟才能运行的批处理作业，也可以使用基于拉取数据的监控方式来对他们进行数据采集，这很有用。这样，您就可以随时间跟踪与其他类型的作业相同的指标，例如与其他系统通信时的资源使用情况和延迟。如果作业开始变慢，这可以帮助调试。

对于经常运行的批处理作业(例如，每隔 15 分钟或更频繁的)，您应考虑将其转换为守护程序，并将其作为离线处理作业进行处理。

### 子系统 <a href="#subsystems" id="subsystems"></a>

除了三种主要服务类型之外，系统还应监控其子部分。

#### 库 <a href="#libraries" id="libraries"></a>

库应提供用户无需额外配置的工具。

如果它是用于访问进程外的某些资源(例如，网络，磁盘或IPC)的库，请跟踪总查询计数，使错误(如果可能发生错误)和等待时间最小。

根据库的重量级，跟踪库的内部错误和延迟以及您认为可能有用的任何常规统计信息。

一个应用程序的多个独立部分可能会针对不同的资源使用一个库，因此请注意在适当的情况下使用标签来区分使用。例如，数据库连接池应区分正在与之通信的数据库，而无需区分 DNS 客户端库的用户。

#### 日志

通常，对于每行日志记录，您还应该增加一个计数器。如果您发现有趣的日志消息，则希望能够看到它发生的频率和时间。

如果同一功能中有多个紧密相关的日志消息(例如，if 或 switch 语句的不同分支)，则有时可以合理地为所有日志消息增加一个计数器。

通常，公开应用程序整体记录的信息/错误/警告行的总数，并在发布过程中检查是否存在重大差异也很有用。

#### 失败 <a href="#failures" id="failures"></a>

失败的处理方式与日志记录类似。每次出现故障时，都应使计数器增加。与日志记录不同，错误可能会产生一个更通用的错误计数器，具体取决于代码的结构。

报告失败时，通常应该使用其他的一些数据指标来表示尝试的总数。这使得故障率易于计算。

#### 线程池 <a href="#threadpools" id="threadpools"></a>

对于任何类型的线程池，关键指标是排队的请求数，正在使用的线程数，线程总数，已处理的任务数以及花费的时间。跟踪队列中等待的内容也很有用。

#### 缓存 <a href="#caches" id="caches"></a>

缓存的关键指标是查询总数，命中数，总体延迟，然后是缓存所在的任何联机服务系统的查询计数，错误和延迟。

#### 收集 <a href="#collectors" id="collectors"></a>

在实施有意义的自定义指标收集器时，建议将收集器所花费的时间(以秒为单位)及遇到的错误数分别导出到计量图表中。

这是可以将持续时间导出为量表而不是摘要或直方图的两种情况之一，另一种是批处理作业持续时间。这是因为两者都代表有关该特定推送/采集的信息，而不是随时间跟踪多个持续时间。

## 需要注意的事情 <a href="#things-to-watch-out-for" id="things-to-watch-out-for"></a>

进行监控时，需要注意一些一般事项，尤其是针对 Prometheus 的事项。

### 使用标签 <a href="#use-labels" id="use-labels"></a>

很少有监控系统具有标签和表达语言的概念来利用它们，因此需要一点时间来习惯。

当您有多个要添加/平均/求和的指标时，它们通常应该是带有标签的一个指标，而不是多个指标。

例如，创建单个带有表示 HTTP 响应状态码的 `code` 标签的 `http_responses_total`的数据指标，而不是不是创建`http_responses_500_total`和`http_responses_403_total`。然后，您可以将全部指标作为一个在规则和图形中进行处理。

根据经验，数据指标名称的任何部分都不能使用程序生成(请使用标签)。除了从另一个监控/工具系统代理数据指标时

另请参阅[命名](https://hulining.gitbook.io/prometheus/v2.19/practices/naming)部分。

### 不要过度使用标签 <a href="#do-not-overuse-labels" id="do-not-overuse-labels"></a>

每个标签集都是一个额外的时间序列，其中包含RAM，CPU，磁盘和网络成本。通常，开销可以忽略不计，但是在具有大量数据指标且跨数百个服务器具有数百个标签集的方案中，这可能会迅速加起来。

作为一般准则，请尝试将指标的基数保持在10以下，对于超出该指标的基数，旨在将它们限制在整个系统中的少数几个。您的绝大多数指标应该没有标签。

如果您的数据指标的技术超过 100 或有可能增长到那么大，请研究其他解决方案，例如减少维度或将分析从监控转移到通用处理系统。

为了让您更好地了解基础的数字，让我们看一下 node\_exporter。node\_exporter 公开每个已挂载的文件系统的指标。每个节点有数十个类似于`node_filesystem_avail`的时间序列。如果您有 10,000 个节点，那么`node_filesystem_avail`的时间序列大约为100,000，这对于 Prometheus 来说是可以解决的。

如果现在要为每个用户添加配额，在 10,000 个节点上有 10,000 个用户时，很快就会达到双百万的位数。对于 Prometheus 的当前实现而言，这太多了。即使数量较少，也存在偶然的成功过高，以至于您无法再在此计算机上拥有其他可能更有用的指标。

如果不确定，请从不使用标签开始，然后随着具体用例的出现逐渐添加更多标签。

### Counter vs. gauge, summary vs. histogram

知道给定度量使用四种主要指标类型中的哪一种是很重要的。

要在计数器和计量表之间进行选择，有一个简单的经验法则：如果该值可以下降，则它是一个计量表。

计数器只能上升(和重置，例如在进程重新启动时)。它对于累积事件数或每个事件中的事物数量很有用。例如，HTTP请求的总数，或HTTP请求中发送的字节总数。原始计数器很少有用。使用`rate()`函数获取它们每秒增加的速率。

计量表可以被设定为某个值，上升和下降。它们对于状态快照(例如进行中的请求，可用/总内存或温度)非常有用。您永远不应该对计量表类型数据指标使用`rate()`函数。

摘要和直方图是更复杂的指标类型，将在[其各自的章节](https://hulining.gitbook.io/prometheus/v2.19/practices/histograms)中进行讨论。

### 时间戳，而不是自事件发生以来的时间 <a href="#timestamps-not-time-since" id="timestamps-not-time-since"></a>

如果要跟踪某件事发生后的时间，请导出发生该事件发生的 Unix 时间戳，而不是自该事发生以来的时间。

导出时间戳后，您可以使用表达式`time() - my_timestamp_metric` 来计算事件发生以来的时间，从而消除了对更新逻辑的需求，并防止了更新逻辑被卡住。

### 内部回路 <a href="#inner-loops" id="inner-loops"></a>

总的来说，工具带来的额外资源成本远远超过其为运营和开发带来的收益。

对于性能至关重要的代码或在给定进程中每秒调用超过 10 万次的代码，您可能需要注意要更新多少个指标。

取决于竞争，Java 计数器多造成 [12-17ns](https://github.com/prometheus/client_java/blob/master/benchmark/README.md) 的时间消耗。 其他语言将具有类似的性能。如果那段时间对于您的内部回路很重要，请限制在内部回路中增加的指标数量，并避免使用标签(或缓存标签查找的结果，例如，Go 中`With()` 或 Java 中`labels()` 的返回值)

还请注意涉及时间或持续时间的数据指标的更新，因为获取时间可能涉及系统调用。与所有涉及性能关键代码的问题一样，基准测试是确定任何给定更改的影响的最佳方法。

### 避免丢失指标 <a href="#avoid-missing-metrics" id="avoid-missing-metrics"></a>

直到某些事情时发生才存在的时间序列是很难处理的，因为通常的简单操作已不足以正确处理它们。为避免这种情况，请预先导出您知道的任何时间序列中的任何时间序列的`0`(或`NaN`，如果`0`会误导)。

大多数 Prometheus 客户端库(包括 Go，Java 和 Python)都会自动为您导出`0`，以获取没有标签的指标。
