仪表化
如何进行仪表化
简而言之,就是尽可能地都进行仪表化。每个库、子系统和服务都应该至少给出几项指标,以帮助我们大致了解其性能。
仪表化应该成为代码的一部分。使用指标类时,请在同一文件中实例化它们。这样,在排查错误时,从告警到控制台再到代码的排查就会变得更加容易。
三种服务类型
对于监控目的而言,服务通常可以分为三种类型:在线服务、离线处理和批处理作业。虽然它们之间存在重叠,但每种服务往往都可以归类为这三类之一。
在线服务系统
在线服务系统是指人类或其他系统期望立即得到响应的系统。例如,大多数数据库和 HTTP 请求都属于此类。
此类系统的关键指标是执行查询的数量、错误和延迟。此外,正在执行的请求数量也可能有用。
对于失败的查询,请参阅下文的“失败”部分。
如果两方的行为不同,在线服务系统应在客户端和服务器端都进行监控,这将对调试提供很大的帮助。如果服务有很多客户端,服务本身逐个跟踪它们显然不切实际,因此它们需要依赖自己的统计信息。
在查询开始或结束时进行计数的模式要保持一致。通常在查询结束时进行计数较好,因为它会与错误和延迟统计数据对齐,并且通常更容易编写代码。
离线处理
对于离线处理,响应并不是马上生成的,并且工作通常会被批量化处理,因此,可能存在多个处理阶段。
对于每个阶段,跟踪输入的项目(items)数量、正在进行的项目数量、上次处理时间以及发送出去的项目数量。如果有批量处理,还需要跟踪进入和退出的批次。
了解系统最后一次处理的时间对于探测系统是否停止运行了而言非常有用,但是,它是一种本地信息。一种更好的探测方法是在系统中发送心跳:假设有一些模拟(dummy)项目,能够通过整个系统并包括插入时的时间戳。如果在每个处理阶段都可以导出它所见过的最近的心跳时间戳,就可以让你知道项目通过系统的传播时间有多长。对于没有静默期(quiet periods)的系统(在此期间不进行处理)而言,你可能并不需要明确的心跳。
批处理作业
离线处理与批处理作业之间的界限有些模糊,因为离线处理可能在批处理作业中进行。批处理作业的区别在于它们不会持续运行,而这使得它们难以被抓取指标。
批处理作业的关键指标是最后一次成功的时间。跟踪每个主要阶段的工作所花费的时间、总运行时间和最后一次完成作业(成功或失败)也是非常有用的。这些都是 Gauges,应将它们推送到 Pushgateway。通常还会有一些针对特定作业的总体统计数据,这些数据也很有用,就比如处理的记录总数。
对于运行时间超过几分钟的批处理作业,也有必要使用基于拉取(pull)的监控程序来抓取它们的指标。这可以让你跟踪与其他类型作业相同的时间序列指标,如资源使用情况和与其它系统通信时的延迟。对于批处理作业开始变慢时的调试工作而言,这很有帮助。
对于经常运行的批处理作业(例如,比每15分钟更频繁),应考虑将其转换为守护进程,并以离线处理的方式处理它们。
子系统
除了三种主要类型的服务外,系统还应监控其子部分。
库
库应提供无需用户额外配置即可使用的监控功能。
如果是用于访问进程以外的一些资源(例如,网络、磁盘或IPC)的库,则至少需要跟踪总查询次数、错误(如果存在错误)和延迟。
根据库的轻重(light/heavy),还应跟踪内部错误和延迟,以及认为可能有用的一般统计信息。
库可能被应用程序的不同部分独立使用,针对不同的资源在适当的地方使用标签进行区分很重要。例如,数据库连接池应该区分它正在通信的数据库,而没有必要区分使用 DNS 客户端库的用户。
日志
一般来说,对于每行日志代码,也应该有一个递增的 Counter。如果你发现有趣的日志消息,你会希望能够查看它发生的频率以及持续了多久。
如果在同一函数中有多个紧密相关日志消息(例如,if 或 switch 语句的不同分支),有时为所有它们共同使用一个递增的 Counter 可能是有意义的选择。
在应用程序整体中跟踪总的信息/错误/告警行数也是一个普遍有效的指标导出方式。在发布的过程中,你可以检查显著的差异迅速发现错误。
失败
失败(failures)应以类似于日志的方式被处理。每次出现失败时,都应该增加一个 Counter。与日志不同,错误也可能根据代码的结构向上传递到更通用的错误 Counter。
在报告失败时,通常应该有一些表示总尝试次数的其他指标,这样依赖,计算失败率就变得很容易了。
线程池
对于任何形式的线程池,关键指标是队列中的请求数量、使用的线程数量、总线程数量、处理的任务数量以及它们所花费的时间。另外,跟踪事物在队列中等待的时间也很有用。
缓存
缓存的关键指标是总查询次数、命中次数、总体延迟,然后是位于缓存链路前端的在线服务系统的查询次数、错误和延迟。
Collector
实现重要的自定义指标 Collector 时,建议导出秒数的 Gauge 和遇到的错误数量的 Gauge。
这是可以将持续时间作为 Gauge 而不是 Summary 或 Histogram 导出的两种情况之一,另一种是批处理作业的持续时间。这是因为这两种代表了特定推送/抓取的信息,而不是跟踪多个持续时间随时间变化。
需要注意的问题
在进行监控时,有一些通用的事情需要注意,也有一些属于 Prometheus 的特定注意事项。
使用标签
很少有监控系统具备标签的概念和有效利用指标的表达式语言,所以使用 Prometheus 标签需要一些适应成本。
当你想要添加/平均/求和多个指标时,处理对象通常应该是具有标签的单个指标,而不是多个指标。
例如,与其使用http_responses_500_total
和http_responses_403_total
,创建一个名为http_responses_total
的单个指标,使它们带有code
标签用于表示 HTTP 响应码更好。之后你可以在规则和图表中以一个整体处理整个指标。
一般的规则是,指标名称的任何部分都不应该由程序生成(使用标签代替)。唯一例外是代理来自另一个监控/仪表化系统的指标时。
有关命名的更多信息,请参阅命名部分。
不要过度使用标签
每个标签集都是一个额外的时间序列,具有 RAM、CPU、磁盘和网络成本。虽然这些开销通常很小,但在具有大量指标和跨数百台服务器的数百个标签集的情况下,这种开销可能会迅速累加。
作为一个一般准则,你的指标的标签基数最好保持在10以下。并且限制整个系统中超过这个基数的指标的数量到几个。绝大多数指标应该没有任何标签。
如果有指标的标签基数超过100或有可能增长到那么大,你可以考虑替代解决方案,例如减少维度的数量或将分析转移到通用的处理系统。
为了给你提供更直观的数字,让我们看看 node_exporter。node_exporter 会为每个挂载的文件系统暴露指标。每个节点将有数十个时间序列,例如node_filesystem_avail
。如果有10,000个节点,你将最终获得大约100,000个node_filesystem_avail
时间序列,这对于 Prometheus 来说是可以接受的。
如果你现在要给每个用户添加类似的配额,那么你很快就会在10,000个用户和10,000个节点上达到几千万的量级。这对于当前的 Prometheus 实现而言确实是太多了。所以你需要记住,即使是较小的数字,也会有成本,同时也意味你无法在这台机器上拥有其他可能更有用的指标。
如果你在开始的时候不确定是否需要标签,你可以先不使用标签,并且随着时间的推移根据具体用例的出现逐步添加标签。
Counter vs. Gauge,Summary vs. Histogram
对于给定的指标选择正确的四种主要指标类型是很重要的。
选择 Counter 和 Gauge 之间的区别很简单:如果值可以下降,它是 Gauge。
Counter 只能增加(并且在进程重启时重置)。它们适用于累积事件的数量,或者在每个事件中某物的总量。例如,HTTP 请求的总数,或者 HTTP 请求中发送的字节数。原始的 Counter 有用的场景较少。你可以使用rate()
函数来获取它们每秒增加的速度。
Gauge 可以设置、增加和减少。它们适用于状态快照,如正在进行的请求、可用/总内存或温度。你不应该对 Gauge 调用rate()
。
Summary 和 Histogram 是更复杂的指标类型,在 Histograms 中讨论。
使用时间戳,而不是事件发生以来经过的时间
如果你想要跟踪从发生某事以来的时间量,导出事件发生的 Unix 时间戳——而不是事件发生以来经过的时间。
通过导出时间戳,你可以使用表达式time() - my_timestamp_metric
来计算事件以来的时间,从而不需要更新逻辑,并保护你不受更新逻辑卡住的影响。
内循环
通常情况下,仪表化给运维和开发带来的好处要远大于其引入的额外资源消耗。
对于在给定进程内部每秒调用超过10万次且性能关键的代码,你可能需要小心关注如何去更新指标。
Java Counter 在有竞争的情况下,每次递增需要的时间大约是12-17纳秒。其他语言也有类似的性能表现。如果这部分时间对你的内循环来说非常重要,那么应限制内循环中更新的指标数量,并尽可能避免使用标签(或者缓存标签查找结果,例如,在 Go 中With()
的返回值,或者在 Java 中的labels()
)。
同时也要额外关注涉及时间或持续时间的指标更新操作,因为获取时间可能会涉及到系统调用。对于任何涉及关键性能代码的情况而言,基准测试是确定变更对性能影响大小的最佳方式。
避免缺失指标
直到发生某些事件才出现的时间序列很难处理,平常的简单操作难以正确处理它们。为了避免这种情况,对于你知道可能会出现的任何时间序列,导出一个默认值,比如0
。
大多数 Prometheus 客户端库(包括 Go、Java 和 Python)会自动为你为无标签的指标导出0
。
该文档基于 Prometheus 官方文档翻译而成。