高可用之路-闲聊监控指标的局限

高可用之路-闲聊监控指标的局限 高可用系列的第一篇。一开始我是想写一个非常宏大的体系大纲但一方面我还没想好怎么设计另一方面我觉得首篇只抛一个框架出来其实有点空泛。所以我就先写一点实际的也是我这几年认识比较深刻的地方吧。熟悉我的人都知道我非常喜欢troubleshooting这几年令我印象深刻的问题都是对于监控指标的解读出了问题所以在高可用这一块认识监控指标的局限性其实是非常重要的毕竟监控是我们的眼睛:)监控指标反映的只是真实的投影监控指标通常是展示采集的数据(Count/Guage等)或对采集的数据运算(QPS/TPS等)。例如我们最常见的指标QPS其含义是平均一秒内有多少次请求。其公式就是:QPS请求总数/总时间这么简单的一个公式其实就蕴含着不小的复杂性。 在公式的分子上-请求总数就很有说法这个请求总数是分布在一台机器上还是在多台机器上。用总数来表达其实就丢失了空间上的分布信息从向量坍缩成了标量是一种投影如下图所示:在公式的分母上-总时间这个也有说法很多监控系统都做不到秒级监控而是分钟级那么100qps可能是均匀的分布在一分钟之内(秒级100)也有可能是仅仅只分布在一秒(秒级6000)内。用60s来计算其实就丢失了时间上的分布信息又是从向量坍缩成了标量又是一种投影如下图所示:在公式的分母上-总时间其实还有其它的变化。例如监控系统每5s采样一次计算出请求总数。但是监控系统本身由于GC的STW或者load高等原因并不是5s精确采样而是漂移到第6s。但分母依旧是用5s来进行计算那么毫无疑问QPS会被计算的偏高如下图所示:为了避免这种现象我们的公式可以修改为:QPS请求总数/时间差如果这个时间差的计算采用墙上时钟(也就是WallClock)那么会由于和NTP授时同步的时候由于时钟跳变(往前或往后拨数秒)而导致QPS的运算出现偏差如下图所示:所以我们需要使用monotonic clock(单调时钟来进行计算)。但即使是如此也无法保证采样任务一定在精确的5s后运行。请求总数丢失了空间分布总时间丢失了时间分布。一旦做了 QPS 这个运算原本“机器维度 × 时间维度”的二维矩阵就被压缩成了一个标量。这个标量当然有用但它已经不是系统真实运行过程本身它只是真实的投影。监控指标无法衡量超出它精度的真实那么如果仅仅只记录数据本身而不做计算是不是能够精确的反映系统的运行过程呢。有时候也不行因为数据本身是有精度的超过这个数据的精度能表达的上限依旧无法反映真实。例如我们最常用的主从延迟指标Seconds_behind_master它的精度是秒。无论监控如何踩点它的原始数据只有0/1/2等整数例如下图所示:笔者就遇到过这样的现象两个一模一样的从库一个从库采样取点基本是0看上去去平均延迟为20ms另一个从库一半的取点为1看上平均延迟为500ms。实际这两的主库延迟是基本一样的远没有(500-20)480ms这样的差距。这其实就和Seconds_behind_master精度只到秒有关。让我们自己观察一下Seconds_behind_master的计算公式:long time_diff ((long)(time(0) - mi-rli-last_master_timestamp) - mi-clock_diff_with_master);关键点在于这个clock_diff_with_master。这边需要先引入一个容易被忽略的常识那就是不同机器的时间戳是不一致的。所以我们在计算Seconds_behind_master的时候需要去掉时间戳不一致的影响也即减去clock_diff_with_master。这个clock_diff_with_master的精度也是秒那么在处理毫秒为单位的时间戳的时候势必存在精度损失。同时这个clock_diff_with_master只会在主从连上的那一刻只计算一次。计算完之后这个精度损失导致的数据差距会一直存在例如我们假设在计算clock_diff_with_master的那一瞬间。从库的clock是0.5s主库的clock是1.0s那么他们的时间差就会由于精度的原因从0.5s放大到1s(MySQL源码中会进行四舍五入)。而这0.5s就会导致实际主从延迟为0的从库在监控指标看来是500ms。计算过程如下图所示:在上图中我们可以看到在我们取从库时钟[0.5,1.5)这个1s的时间段范围内。在前0.5s也就是[0.5,1)这个区间中我们计算出来的Seconds_behind_master是-1然后由MySQL源码强行校正为0而在[1,1.5)区间计算的确是1 。那我们的平均值就可以计算出来为(0.5*00.5*1)/(1.5-0.5)0.5500ms!从上面这个例子就可以看到用只有秒级的监控指标来观测毫秒级的主从延迟差距完全没有意义Seconds_behind_master这个指标只能反映秒级的变化。它只能回答“有没有秒级延迟”这个问题不能拿来回答“有没有几百毫秒延迟”这个问题。指标一旦被拿去回答超出它表达精度的问题就会从工具变成误导。如果想看详细的分析可以看笔者之前的文章:https://mp.weixin.qq.com/s/YBLxEPbaNAYxjUYGhmIl_g监控指标无法突破采集环境的边界那么如果我拥有无限的精度能否精确的反映系统的运行过程呢有时候也不能做到因为指标是在环境中采集的环境本身限制了它的表达。例如在容器中cpu busy的计算是 :容器运行的时间/总时间 注:实际可能是 CPU.busy (cpuacct.usage(T秒) - cpuacct.usage(T-5秒))/((5秒)*CPU核数)如果容器中监控指标CPU.busy50%就认为系统还处于健康水位就很有可能被误导。在容器超卖(例如64核的物理机分配了总共128核的容器应用)。在所有应用都很繁忙的情况下容器中的CPU.busy最多只能达到50%就被其它繁忙的容器抢占。物理机CPU利用率可能已经到了100%而容器的CPU只能到50%。如下图所示:那如果我在物理机上直接采集机器指标那么是不是就完全准确呢也不一定以x86为例会有一些SMM(System Management Mode)中断一旦进入了这种中断(一般是温控/ECC检查/电源管理等)Kernel会被强行block完全感知不到那么Kernel管理的user/system time都不会有变化。但可能真实时间已经100ms过去了唯一的感觉可能只有应用的超时报错但仅仅从Kernel本身的监控完全不知道发生了什么如下图所示:笔者就有一次发现线上应用出现报错尖刺看了所有监控指标都没有异常但奇怪的是都落在一台宿主机上。最后发现那台宿主机的温度监控值有变化推断是 SMM在执行温控/电源管理之类的操作。但Kernel完全不知道这段时间发生了什么user/system time 没有任何记录。 唯一能感知到的就只用应用层的超时报错。监控指标无法无限的逼近真实真实的物理世界肯定是无法观测完全的。这里定义的真实其实是能够知道系统在任意时刻所有的状态。例如对于一段时间的请求我们能够拿到这个请求走过的所有代码路径以及所有的耗时那么我们其实就可以进行任意的剖析。但很可惜的是这些数据太过于庞大如果硬要收集反而让系统本身消耗在大量的指标计算和收集上进而导致了性能极度恶化。也就是观测的越深入越全面观测本身所造成的干涉越大直到测量本身成为了更大的问题观测到的真实已经和想观测的真实相去甚远。在这里举一个例子。MySQL 的监控非常依赖 show processlist 和 show status 这些命令而这些命令对线上实际执行的 SQL 有不小的影响。具体过程如下图所示:写SQL在执行的时候会生成binlog并进行组提交在组提交内部会调用publish_coordinates_for_global_status来记录当前binlog的Position此时需要加锁Lock_status。在MySQL监控命令show status的时候也会加锁Lock_status来获取一致的统计数据信息。尤其是在其对每一个thd(MySQL线程)做统计的时候为了保护内部结构也需要加锁LOCK_thd_list而在MySQL的另一个监控命令show processlist时候也是会对LOCK_thd_list加锁。一旦监控脚本执行过于频繁就极其容易导致 SQL 执行被 block 住。甚至线上因此产生过主从切换在我们线下复现的过程中发现高频率的show processlist对于性能有超高幅度的影响(会使得TPS从9000跌到3000甚至一度出现跌0的现象)。监控指标的准确性受制于监控系统在微服务的今天监控指标基本都是通过数据采集并上报给监控系统并由监控系统统一运算得出。而海量的监控数据收集与运算必须分布在多台计算节点上最后再进行统一的汇总。那么计算节点本身也会出现宕机/GC/block等现象一旦出现这种情况势必会导致不准确例如本因在第30分钟计算的数据在第31分钟才计算完毕导致第30分钟的QPS变低而第31分钟的QPS变高。亦或者直接宕