更多人在用传统的Nagios,Zabbix等监控工具。Jason Dixon在2012就意识到这些工具的问题是每个人都想做到大而全,实际上我们更需要的是一对小二精的组件拼装成一个个性化的解决方案。推荐大家去看一下他的演讲视频:https://speakerdeck.com/obfuscurity/the-state-of-open-source-monitorin...
这是Jason Dixon所构想一个组件图。他认为不同的开源方案应该专注于提供好这些组件。
Caskey Dickson 也有同样的设想,并且提出目前的很多组件仍然是缺乏好的提供者的(比如海量metric存储和任意维度聚合):https://www.usenix.org/conference/lisa13/working-theory-monitoring
这是他在ppt里画的一个组件图,并且评价了一下主流的开源组件。
下面按照顺序解释一下这个告警平台流程图中的各种组件:
- 采集/收集:数据可能来自于业务的db,可能来自于日志文件,可能是由业务程序内置上报的。通过各种手段采集收集到“原数据库”里。什么是“原数据库”?比如kafka队列。比如logstash上报前把数据汇总到的redis数据库。“原数据库”的存在是为了把分散的数据汇总到一处,方便后续的处理。
- 索引:索引主要是为了日志存在的。为了让日志可以检索,需要把日志数据进行切分,提取出字段和关键字录入到“检索库”里。这就是著名的ELK最擅长的事情。Logstash负责索引操作,Elasticsearch充当检索库的角色。
- 统计:指标库最常见的就是给每个ip存放一份cpu使用率的时间序列。对于这种情况,原数据采集了之后直接录入指标库就行了。另外一种比如是nginx的access log,采集到之后需要经过统计才能得出某某url在5分钟内被访问了xx次的数据。统计最简单的形式比如statsd,复杂的可以用storm写自定义的流式计算任务,更复杂的甚至涉及机器学习,比如summo logic。指标库一般使用的是opentsdb等时间序列数据库,但是我强烈推荐Elasticsearch:http://taowen.gitbooks.io/tsdb/content/
- 异常检测:传统的告警就是比对一个静态的阈值。对于错误率,访问延迟等指标用静态阈值确实是没有问题的。但是对于5分钟内的收入,访问人数等综合的业务指标很难用静态阈值去做检测异常。复杂的异常检测会利用曲线的时间周期性,和相关曲线之间的相关性去定义动态的阈值。etsy的skyline是开源组件里比较著名的一个。
- 告警:告警和异常检测是两个过程。不是每个异常都值得通知运维跟进处理(起码可以做一个频率收敛),也不是把原始异常以xx小于xx这样的形式告诉给运维就可以了(可以把告警相关的故障一起通知了)。这里个从异常到告警的过程需要做到确认这个异常是一个值得通知的告警,并且能够做一个初步的故障定位。最简单的定位的手段是就把其他部门的告警(比如网络部门的网络质量告警,安全部门的DDoS告警),以及流程单据(发布单)做为事件纳入事件库。通过查询事件库定位原因。
在这样的一个提下下,很多零散的工具做的事情被整合在了一起:
- 拨测:定时curl一下某个url,有问题就告警。这个是走 原数据=>直接录入为异常=>告警
- 日志集中检索:ELK的经典用法。走 原数据=>检索库
- 日志告警:5分钟Error大于xxx次告警。走 原数据=>实时统计出指标=>检测异常=>告警
- 指标告警:cpu使用率大于xxx告警。走 原数据=>录入到指标库=>检测异常=>告警
把不同的告警和监控策略整合到同一个数据管线的好处是简化了整体的架构,剔除了重复的模块(比如上报和原数据汇总等模块)。而且有利于各司其职,专业化纵深发展。目前开源组件还比较缺乏的有这么几块:
- 指标库需要海量存储海量聚合能力,开源的有 Druid.io Elasticsearch Crate.io 等
- 异常检测,缺乏真正实用的。算法其实不用很复杂
- 故障定位和收敛,缺乏真正实用的。Flapjack的实现太简单了,Riemann又太小众了
- 实时统计,缺乏成熟的解决方案。Storm就是一个底层的执行引擎,而Spark还缺少时间窗口等抽象。
- 日志自动分类,还没有开源工具可以做到 summo logic 那样的效果
- 自定义曲线和仪表盘:类似kibana的工具还是太少
告警对象
告警对象可以分为两种:
- 业务规则监控
- 系统可靠性监控
对于业务规则监控可以举一个游戏的例子。比如DNF的游戏角色在一定装备的情况下,单次打击的伤害输出应该是有一个上限,如果超过了就说明有作弊的情况。又比如斗地主游戏里一个人的连胜场次是有一定上限的,每天的胜率是有一定上限,如果超出平均值太多就可能是作弊。业务规则监控的不是硬件,也不是软件是否工作正常。而是软件是否按照业务规则实现的,是否有漏洞。也可以理解为对“正确性”的监控。
系统可靠性监控是最常见的监控形式,比如发现是不是服务器挂掉了,服务是不是过载了等等。对于大部分后台服务,系统可以抽象建模成这个样子:
对于这样的系统可以采集什么指标?
- 请求数,请求到达速率
- 正常响应数,正常响应占比
- 错误响应数,错误响应占比
- 响应延时
- 队列长度,排队时间
实际的情况是,几乎任何系统都不是孤立运行的。而是这样的:
一个DB会依赖于底层的cpu,内存,磁盘等资源。一个Http服务会依赖于底层的DB服务。一个应用会依赖于数个底层的RPC服务。于是又多了几个指标
- 资源A的调用量(比如CPU使用率)
- 资源B的调用量(比如内存分配和释放)
- 资源C的调用量(比如网络发送包量)
- ...
这种层次结构,一般来说简单来说可以分为四层:
- 产品策略和营销:它们决定了根本的请求到达的速率
- 应用层(更粗俗一点可以叫web层):最上层的胶水
- 服务层:db,各种RPC服务,以及层层嵌套的服务
- 硬件层:cpu,内存,磁盘,网络
因为这样的一个依赖层次。上一层对下一层的资源消耗量变成了下一层的请求数。比如Http服务消耗了多少DB的资源,就对应了DB服务需要处理多少请求数。DB繁忙与否取决于Http服务请求,Http服务请求繁忙与否取决于多少人打开客户端,多少人打开客户端又取决于产品策略和营销活动。这种层次结构决定了单纯跟踪一个指标,比如绝对请求数,很难说明这一层的服务是否出现了故障。
有这么多层次,每层又有很多指标可以采集。那么应该采集什么指标,用什么告警策略去告警呢?最前面已经提到了告警必须是actionable的,但是实际情况下只有这种纲领性要求仍然是不好操作的。至少可以提几点不应该做的事情:
- 不应该用采集的难度决定你使用什么指标去告警。很多情况下cpu使用率可能是最好采集的,但是未必是最值得告警的。
- 不要给运维他们想要的告警,而是要做“真正”想要的告警。大部分情况下,人们告诉你的是一个解决方案。运维告诉你它需要对db进程的cpu使用率超过x%的时候告警,它给你的是一个他认为最优的解决方案。但是他真正想要的是知道db服务是否有异常,cpu使用率超过x%未必是最好的告诉你服务是否出现异常的指标。
盲目地采集那些容易获取的指标,并随意地设定阈值告警是大部分糟糕的告警质量的根源。
监控的指标和策略
那到底应该采集什么指标呢?我认为大部分的系统可靠性监控不外乎三个目标:
- is the work getting done?系统是否在持续完成其设定的工作。
- is the user having good experience?用户体验是否好。
- where is the problem/bottleneck?问题或者瓶颈在哪里。
其中最核心最关键的是第一个问题,is the work getting done。对于数据库来说,我们可以采集:
- cpu 使用率
- 网络带宽大小
- db请求数
- db响应数
- db错误响应数
- db请求延迟
显然要回答一个db是否完成了其指定的工作,更应该关注的指标是这两个:
- db请求数的绝对量
- db正确响应相对请求数的占比
这两个指标相对于采集什么cpu使用率更能说明问题。不仅仅是db,各个层次的服务都可以用请求量和正确响应占比来反映其工作状况。比如http请求数(对比http正确响应数),比如app打开次数(对比服务端记录的在线人数)等等。
为什么cpu使用率不能说明问题?大部分时候,我们并不关心cpu本身,而关心使用cpu为资源的服务。所以cpu使用率只是一种资源的请求数而已。与请求数相关的一个概念是saturation(上限),当上限达到的时候,处理开始排队,延迟开始变长,错误率开始升高。那么cpu使用率是不是能够说明上限呢?cpu使用率的上限以100%记,那么90%开始告警不是很合理吗?毕竟cpu 100%了几乎可以等同于db无法正常处理请求了。
这种利用底层资源调用量,评估其是否达到上限的做法有两个根本缺陷:
- 你无法知道上层服务可以把底层资源利用到什么程度
- 底层资源的 saturation 未必可以容易度量
具体来说,db是不是可以真的100%利用cpu是位置的。假如请求里锁,或者sleep,那么也许cpu永远也无法达到100%。90%可能就是极限了。而且现代的cpu是多核的,如果请求处理只能利用单核,处理在多个核之间跳跃,对于一个核来说永远也不会一直保持100%。
对于cpu可能其上限真的有一个100%的值。但是对于很多非硬件的服务,比如你是一个登陆服务,依赖于一个db。那么这个db每秒可以处理的不同sql组合数是很难度量的,绝非和磁盘一样有一个mb/s的极限绝对值可以做为对比。
而且度量底层资源的使用还有一个缺陷是你无法枚举出所有依赖的资源的。所以与其这么绕弯子地通过底层资源来间接监控上层服务是否正常,还不如直接测量work是不是getting done呢。
对于第二个问题,is the user having good experience?可以采集的指标为
- 平均排队时间,平均总响应延迟
- 99/95/90 percentile的排队时间,99/95/90 percentile的响应延迟
这里的用户不一定是指人或者玩家,可能是上一层的服务调用方,另外一个系统。
第三个问题就是所谓的故障定位。要是人工来做的话,最常见的做法是收到了告警,然后登陆CRT,开始敲各种命令查找原因。对于系统来说,最合适的做法不是出了问题再去执行一堆命令,而是:
- 每个层次都对自己做告警
- 顶层服务出了告警触发自动定位程序
- 按照服务的依赖关系和大致的时间范围,定位到告警之间的关联,从而找到出问题或者瓶颈的地方
当然实际情况是很复杂的。很多原因和结果是互为因果的。两个告警是两个现象,还是一个原因一个现象实际上很难说得清楚。
从告警算法的角度来讲,对成功请求率,或者平均响应延迟做告警是非常容易的。静态阈值大家看不起,觉得简单。但是大部分告警用静态阈值就可以解决问题。
理论与现实
那告警要不要高难度的算法?我的观点是采集到了正确的指标,是不需要复杂算法的,就是静态阈值都可以搞得定。但是至少有三种场合需要算法:
- 无法直接采集到错误数:需要对错误日志的自动分类
- 无法直接采集到请求成功率:需要对请求数或响应数的绝对值做异常检测
- 只有总数,无法采集到其中的每个细分构成项的占比:需要对参与的factor进行算法拟合
其实这三项都是一个主题的,当你无法直接获取到告警所需的指标的时候,事情会变得复杂很多。有一个比喻是:最近NASA宣布的地球孪生兄弟Kepler 452b。如果我们的探测器可以跑到1400光年之外,发现他将是非常容易的事情。正式因为直接获得数据非常困难,所以科学家才需要根据行星阻挡恒星时引起的亮度变化(所谓掩星法)来发现这些遥远的星球。
采集所需的指标的困难可能是几方面的因素。一种原因是采集本身是非常消耗资源的事情。比如获取每个mysql查询所消耗的cpu。跟踪每个请求处理过程是不可能的。这个时候就需要算法的帮助了,可以仔细看一下vividcortex的视频:http://www.youtube.com/watch?v=szAfGjwLO8k
更多情况是采集指标困难是D/O分离造成的沟通问题,运维需要的指标需要开发去埋点,而开发埋点的地方又需要运维去做告警。很多时候退而求其次就会造成,有什么指标就用什么指标的状况。比如虽然没有请求响应的错误数,但是错误基本上都会有错误日志记录,根据错误日志滚动的快慢可以大致知道是不是出了问题。这就引入了一个非常困难的日志分类问题,什么日志代表了正常,什么日志代表了异常,异常又非了哪些类型?这个方面算法做得好的是summo logic公司:https://www.sumologic.com/ 。为什么这种opsdev(嘲讽devops那)公司如此热衷于算法?对于他们来说好处是显而易见的,客户需要做的改动越少,接入成本越低,客户面就越广。但是拿机器算法去挖掘海量日志真的是回答:is the work getting done?的最佳手段?显然不是。这就是大炮打蚊子。日志的存在是用于解决问题,而不是有了海量日志了,如何用好“它们”变成了问题本身。
第三类情况是没有办法采集到请求成功率,只能对绝对的处理成功的量。只有这类数据要告警,就无法做简单的静态阈值了。对于延迟,一般可以定一个业务上可以接受的延迟上限。对于成功率,也可以定一个可接受的成功率上限。但是对于绝对的处理量,是没有办法简单地比较一个静态阈值就可以判断是正常还是异常的。
在讨论如何实现之前,再强调两点:
- 处理成功的量不是度量is work getting done的最佳指标。费事费力去搞算法,不如直接把成功率指标给采集了。
- 处理成功的量,还取决于请求数。而请求数根本上是取决于上层服务了。你是一个dba,发现db的每秒处理的请求数陡降了。这说明是db故障了?还是app故障了?都有可能……最最上层是产品和营销。你发现一个业务的注册量相对前几天变少了,这个是不是说明注册服务出问题了?也需是产品太烂了,游戏根本没有人来玩。也可能是营销手段的营销,不送金币了,玩家没积极性了。
异常检测
只有请求数,没有参考的上限值(saturation),也没有成功率,没有失败率,怎么检测异常?
上图的黄线是昨天的值,绿线是今天的值,大部分服务监控的曲线图都长这样。可以得出四个思路:
- 曲线平滑:故障一般是对近期趋势的一个破坏,视觉上来说就是不平滑
- 绝对值的时间周期性:两条曲线几乎重合
- 波动的时间周期性:假设两个曲线不重合,在相同时间点的波动趋势和振幅也是类似的
- 有一个长度可观的坑:当曲线开始回升到历史范围的时候,一般可以确认这个时间段是真的故障了
从这四种直觉展开,可以得出各种或复杂或简单的算法。下面要讲的算法都是非常简单的,无需很高深的数学知识。具体这里不展开,有兴趣的请看下面链接
- 参考:https://segmentfault.com/a/1190000003012335