春秋战国时期,有位神医被尊为“医祖”,他就是“扁鹊”。一次,魏文王问扁鹊说:“你们家兄弟三人,都精于医术,到底哪一位最好呢?”扁鹊答:“长兄最好,中兄次之,我最差。”文王又问:“那么为什么你最出名呢?”扁鹊答:“长兄治病,是治病于病情发作之前,由于一般人不知道他事先能铲除病因,所以他的名气无法传出去;中兄治病,是治病于病情初起时,一般人以为他只能治轻微的小病,所以他的名气只及本乡里;而我是治病于病情严重之时,一般人都看到我在经脉上穿针管放血,在皮肤上敷药等大手术,所以以为我的医术高明,名气因此响遍全国。”

监控在企业应用系统中属于不可或缺的基础建设,一个完善的监控体系可以帮助我们发现系统瓶颈、了解系统状态,最重要的是发现系统故障。

一些小型企业系统,可能更多的利用云产品自带的系统监控来监控系统CPU、磁盘、内存等指标,对程序运行只能通过日志来监控,比如定期grep一下日志中的Exception,或者Catch住异常然后发一份邮件,或者利用JMX来监控(但这个需要一直有人盯着)。 这种方式便捷又原始,不过也是,小型系统如果为了监控去引入Cat、Prometheus、Skywalking这类组件,从成本上来说得不偿失。

这种在不能引入第三方组件的情况下,可以考虑对系统原有的Log组件做一些封装,以Slf4j为例,我们可以通过实现它的Filter来做日志拦截,如果有Error日志,就通过钉钉或者企业微信的WebHook机器人进行统治,将异常情况实时通知给业务开发者。 当然这个组件可以像上面这样简单实现,也可以做的很复杂,比如做Error日志通知的黑白名单,除了Error日志,我们还可以在Http的Filter上判断接口响应时间,如果接口处理超过一定时间就进行告警,此外Dubbo的Filter也是同理,如果系统又配置中心这一概念,还可以玩的更灵活。 当然,为了防止Error日志请求钉钉的耗时影响业务,这部分逻辑可以异步去处理,我们使用的组件是通过时间轮去异步发送钉钉消息,并且对时间片中相同的日志进行了合并。

上面这种方式是比较简单粗暴的,它可以解决大部分的问题,如果你的Log打的比较规范,在系统崩溃前就可以收到很多告警消息,最直观的就是请求响应超时的告警突增,这些都预示着你的系统有“病”,但是这种简单粗暴的监控没法告诉你是什么病,这时候系统压力已经濒临极限,就需要扁鹊来穿针放血,排查系统问题,最有效的办法是直接重启系统,但这会丢掉病人发病时第一时间的资料,堆栈信息。

当然,现实情况可能比较复杂,说不定发生故障时系统已经封闭,进行上下线重启操作,或者权限管理比较严格,无法第一时间连上故障机器进行排查,只能去Review代码或者排查日志,都不好说。


智者千虑,必有一失。愚者千虑,必有一得。

这句话出自《晏子春秋》,用在监控系统也再合适不过,就算设计的再精妙的监控系统可能都逃不过系统病情严重后扁鹊出手,线上故障是无法避免的。所以监控系统应该作用于我们开发的三个阶段。

开发时

开发中我们需要一个充当扁鹊大哥的角色,帮助我们直接发现代码问题,比较直接的方式就是组内的Code Review,严格代码提交规范,再业务再老道的人也难防提交一个Bug。此外就是引入Sonar这类静态代码扫描工具来排除隐藏Bug,以及规范我们的代码提交。

运行时

这时监控系统的关键,这部分需要依赖第三方组件。

比如做链路监控的Skywalking、Cat,Skywalking通过给我们的请求添加TraceId,来分析从用户发起请求到结束经过了那些系统处理、耗时、缓存、数据库访问等等。此外点评开源的Cat,功能强大,可以收集接口的平均响应时间、99线、95线、最大最小响应时间、失败率等信息,还有接口的RT图,系统上下游调用分析,堆栈快照,告警等功能,无论是系统性能优化时分析瓶颈,还是排查问题,Cat都是得力帮手,不过Cat现在的社区不是很活跃,对一些新技术支持不是很完善。

除了调用链路的监控,还有我们机器性能指标、业务监控等需求,常用的方案是Prometheus(普罗米修斯),Prometheus支持用户利用其API自己开发Node Exporter插件,去监控不同的中间件指标,Prometheus默认的Node Exporter主要用于监控机器性能,还有一些第三方开发的Mysql Exporter、RocketMQ Exporter可以用于监控Mysql节点、RocketMQ的IO、生产消费量,消息大小等指标。

Prometheus的指标主要有四类:Counter,Gauge,Histogram,Summary。

  • Counter
    连续增加不会减少的计数器,例如:网站访问人数,生成请求次数、错误次数等指标。Counter类型的指标,只包含一个inc()的方法,就是用于计数器+1
  • Gauge
    一个可增可减的动态指标值,可以用来统计 如CPU,内存使用率,线程池数量等,包含两个主要的方法inc()和dec(),用于增加和减少计数
  • Histogram
    指标生成的是直方图数据,主要用来统计数据的分布情况,类似一段时间内http请求响应小于0.005秒、小于0.01秒、小于0.025秒的数据分布情况
  • Summary
    Summary 类型是在客户端直接聚合生成的百分位数

同样,我们在设计监控项时需要考虑到Google提出的四个黄金指标。

  • [X] 延迟:服务请求所花费的时间,需要区分成功请求和失败请求。例如,失败请求可能会以非常低的延迟返回错误结果。
  • [X] 流量:针对系统,例如,每秒HTTP请求数,或者数据库系统的事务。
  • [X] 错误:请求失败的速率,要么是HTTP 500错误等显式失败,要么是返回错误内容或无效内容等隐式失败,或者基于策略原因导致的失败——例如,强制要求响应时间超过30ms的请求视为错误。
  • [X] 饱和度:应用程序有多“满”,或者受限的资源,如内存或IO。这还包括即将饱和的部分,例如正在快速填充的磁盘。
故障时

故障处理也应当属于我们监控系统的一部分,在这部分,我们需要在排查到问题原因后,对故障期间的错误数据进行修复,这就需要我们自己通过业务逻辑去开发相应的工具箱以及建立完整的SOP体系了,当然,这不是一蹴而就的事,需要在平常问题修复排查时进行沉淀。

为了防止故障发生,一些复杂的业务逻辑上线前一定要做灰度开关、功能开关或者降级服务,不需要设计的多么精妙,一个简单的if逻辑判断即可,保证业务可以平稳运行后,日后删掉这部分开关再上线。降级开关可以用在一些预估有大流量或者接口本身响应时长就高的情况,防止系统或者下游被大流量直接打垮。


最后,本文只是简单提了一些系统指标监控、故障监控,此外还有性能监控,是我们进行性能分析、性能优化的关键,这部分监控需要深入到应用程序运行时类加载、方法耗时、系统API、网络请求首包耗时、DNS解析耗时的层次。建立一个精细的监控平台,是一个需要长久时间发展演进的过程,不管是千虑一得,还是千虑一失,都是难以避免的,当然最终要的是在血的教训中积累经验,避免问题再次发生。