• instrumentation
    • How to instrument
    • 三种类型服务
      • 在线服务系统Online-serving systems
      • 线下处理 offline processing
      • 批量任务
      • 子系统Subsystems
      • Libraries库
      • 日志 Logging
      • Failures 失败
      • 线程池 TreadPools
      • Caches缓存
      • Collectors收集器
    • 关注的事情 Things to watch out for
      • 使用标签
      • 不要过度使用标签
      • Counter vs. gauge, summary vs. histogram
      • 时间戳Timestamps, not time since
      • 内部循环 Inner loops
      • 避免丢失的度量指标

    instrumentation


    这篇文章为检查代码提供了一套指导方针。

    How to instrument

    简短地回答,检查所有代码。每个库,子系统和服务应该至少有几个指标,让你大概了解其性能。

    检查应该成为代码中的一个组成部分。在相同文件中,你可以使用度量指标类的实例化对象。当您正在追踪错误时,这将从警报传递到控制台变得更为简单。

    三种类型服务

    按照监控意图,服务可以分为三类:在线服务online-serving, 线下处理offline-processing和批量任务batch jobs。他们之间有重叠,但每个服务都擅长处理内部事务。

    在线服务系统Online-serving systems

    一个在线服务系统是希望能够获取立即响应。例如:大多数数据库和http请求都归类在其中。

    这个系统中的关键度量指标是执行查询次数,错误和延迟。正在进行请求的数量也是有用的度量指标。

    对于查询失败次数的统计,请移步Failures。

    在线服务系统应该在客户端和服务端都有监控。如果这两边出现不同行为,对于用户调试,是非常有用的信息。如果一个服务有多个客户端,那跟踪所有的客户端行为是不切实际的,因此它们必须依赖自己的统计数据。

    当你查询开始或者结束时,统计查询的一致性。当建议查询结束时,它会排除错误和延迟的统计信息,并且往往易于编码。

    线下处理 offline processing

    对于线下处理,表示用户不需要等待立即响应,所以批量任务处理是非常常见的。这里有一些处理阶段。

    对于每个阶段,跟踪进入的items,处理中的数量,上次处理任务的时间和发送的items数量。如果批量处理,你也应该跟踪进出的批次。

    如果它已经停滞了,则知道上次你处理items的时间是非常有用的,但是它是本地化信息。一个更好的方案是通过系统发送一个心跳:通过所有流程的items, 在插入时写入了时间戳。每个阶段能够导出最近心跳时间戳,可以让你知道item通过整个系统阶段需要多长时间,以及每个阶段花费的时间长度。对于没有进行处理空闲时段的系统,可能不需要明确的心跳。

    批量任务

    在线下处理和批量任务之间有一根模糊的线,因为在批处理任务中可以做线下处理。批处理任务的区别在于它们不连续运行,这使得获取度量指标非常麻烦。

    批处理任务的关键度量指标是它上次成功处理的时间戳。跟踪一个任务在每个阶段花费多长时间也是非常有用的,整体运行时间和这个任务完成的最后时间戳(成功或者失败)。这些都是gauges,应该被推送到PushGateWay。通常还有一些整体的特定于工作的统计信息可用域跟踪,例如:处理的记录总数。

    对于需要花费几分钟的批处理任务,通过基于监控的pull来获取度量指标也是非常有用的。这可让你随着时间的推移跟踪与其他类型的作业相同指标,例如:资源使用量以及和其他系统通信的延迟。如果这个任务开始变慢,则它能调试诊断。

    对于频繁(每15分钟)运行的批处理任务,你应该考虑把这些批处理任务常驻在后台,并通过线下处理任务来处理它们。

    子系统Subsystems

    除了这三种主要类型的服务,系统也可以允许监控部分子系统。

    Libraries库

    库应该提供无需用户额外配置的检查。

    如果一个库,它常常被用于访问这个进程的一些外部资源(如:网络,磁盘和IPC通信),那么这个库需要跟踪整个查询数量,错误和最小延迟

    依赖于这个库本身的大小,跟踪库的内部错误和它自己处理的延迟,你所想到的一般统计都是非常有用的

    库可以用于不同资源的整个应用程序的多个独立部分使用,因此请注意在适当的情况下将用途与标签区分开来。例如,数据库连接池应该区分正在谈论的数据库,而不需要区分DNS客户端库的用户。

    日志 Logging

    作为一般规则,对于每行日志代码,你都应该有个增量统计。可能你想找到一条有意思的日志信息,来确定它出现的频次和间隔时间。

    如果在同一个function中(例如:case或者if语句不同的分支)有非常相似的日志信息,有时候把它们看做同一个统计变量是非常明智的做法。

    导出整个应用程序info/error/warning的日志记录总数通常是有用的,并检查这些差异是发布过程的一部分。

    Failures 失败

    Failures的处理和日志类似。每出现一次失败,统计失败变量增加1。和日志不同的是,这个错误也可能会上升到一个普通统计变量,这取决于你的代码结构。

    当上报失败时,你通常应该有一些其他指标,表示总尝试次数。这使得可以统计出失败率。

    线程池 TreadPools

    对于线程池的任何排序,这个关键度量指标是队列的请求数量,正在使用的线程数量,总的线程数量,已处理的任务数量和需要花费的时间成本。跟踪每个任务在线程池队列中等待的时间也是非常有用的。

    Caches缓存

    对于缓存,关键指标是总的查询次数,命中次数和查询统计,错误数和任何在线服务系统的缓存在前面的延迟

    Collectors收集器

    当实现一个非实验的自定义度量指标收集器时,建议您输出一个量表,以确定收集所需的时间在几秒钟内,以及另一个导出所遇到的错误。

    这是两种情况之一,可以将持续的时间作为度量标准导出,而不是汇总或者直方图,另一种是批量处理作为持续时间。这是因为两者都代表关于该特定push/pull的信息,而不是跟踪多个持续时间。

    关注的事情 Things to watch out for

    当我们做监控时,有一些常见事情需要注意,特别是Prometheus规定的。

    使用标签

    少数监控系统有标签和表达式语言的概念来利用它们,所以需要一些习惯。

    当你有想要的add/average/sum的多个度量指标时,它们应该是一个带有标签的度量指标,而不是多个度量指标。

    例如,可以创建一个度量指标带有标签为http响应码codehttp_responses_total,而不是创建两个度量指标http_resonses_500_totalhttp_reponses_403_total。这样你就能够按照规则和图表,统计到这个度量指标样本中。

    作为经验法则,不应该程序化地乘车度量名称的一部分(使用标签代替)。一个例外是从另一个监控/检测系统代理指标。

    见naming

    不要过度使用标签

    每个标签集是一个额外的时间序列,包括RAM,CPU,磁盘和网络成本。通常开销是可以忽略不计的,但是在数百个服务器上有很多度量指标和数百个标签的场景中,这可以快速增加。

    作为一个指导,尝试将度量指标的基数保持在10个一下,并且对于超过此数量的度量指标,旨在将其限制在整个系统中。绝大多数的度量指标都应该没有标签。

    如果有一个基数超过100的度量指标或者可能迅速增长的度量指标,那么可以调查替代解决方案,例如:减少维度数量或者把分析问题的角度从监控转移到通用处理系统上。

    如果你不确定,在没有标签的情况下,随着具体用例的出现,随着时间的推移添加更多的标签。

    Counter vs. gauge, summary vs. histogram

    非常重要的一点是要知道,用于给定度量指标的四种主要度量指标类型。

    在counter和gauge之间挑选,有一个简单的经验法则:如果value可以下降,则使用gauge度量指标。

    Counters只能上升(也能够重置,例如:服务重启)。累计事件数量,和每个事件的数量。例如:http请求数量,或者http请求发送的字节数量。原生counters很少用。使用rate()函数获取每秒增长速率。

    Gauges能够被设置,上升和下降。它们对快照的状态有用,例如:处理中的请求,可用/总内存或者温度。永远不要使用在gauge上使用rate()函数。

    Summaries和Histograms是非常复杂的度量指标类型,单独讨论

    时间戳Timestamps, not time since

    如果要跟踪事件发生的时间,请使用时间戳,而不要用当前时间。

    当时间戳导出后,你可以使用表达式time() - my_timestamp_metric去计算时间发生了多久。移除了对更新逻辑的需要,并保护你免受更新逻辑的困扰。

    内部循环 Inner loops

    一般而言,检查带来的资源消耗远远不及它带来的操作和开发的优势。

    对于指定进程,代码(1s调用超过100k)的执行性能是非常关键的,你可以花费更多的心思关注度量指标更新的数量。

    一个java counter的加1操作花费了12~17ns,这主要取决于竞争条件。其他语言也有相似的性能。如果内部循环的时间量很大,则限制在内循环中增加度量指标数量,并且尽可能地避免使用标签(或者命中标签查找的结果,例如:Go中的With()返回值或者Java中的labels())

    还要意识到时间或者持续时间的度量指标更新,因为获取时间可能涉及系统调用。与涉及关键性代码的所有事项一样,基准是确定任何给定变更的影响的最佳方式。

    避免丢失的度量指标

    在事情发生之前不存在的时间序列很难处理,因为通常的简单操作已经不足以正确处理他们。为了避免这种情况,你可以事先在你知道的时间序列样本中导出0(或者如果0产生误导,则返回NaN)

    大多数Prometheus客户库(包括:Go,Java和Python)将为您自动导出0,用于没有标签的指标。