Mtail 导致 Nginx 报警延迟
问题描述
我们当前在用的 Nginx 监控系统主要存在晚高峰数据延迟和偶发性数据断层两个问题,具体表现就是晚高峰时监控数据总是延迟 3 分钟以上,极端情况下还会出现监控数据完全停止的情况,导致了 Nginx 短信电话报警中 80%都延迟了 3 分钟以上,严重影响了报警时效性和数据准确性。
原因分析
Nginx 监控的简化架构大致如下图,可以发现日志数据从 Nginx 机器 >>> 大数据计算平台 >>> 监控中心 的传输链路有点长,而大流量的传输往往很容易受内网网络波动影响;预聚合服务则是一个运行在大数据平台上的 Flink 任务,主要功能是对全量日志数据进行清洗和预聚合, 稳定性易受晚高峰流量突增和宿主机器资源的影响,极端情况下 flink 任务还会出现宕掉的情况,需重启丢弃数据才能恢复。
优化重构
业界关于 Nginx 监控的主流的监控方案主要有以下三类:
远端处理日志:基于 filebeat、logstash 之类的日志采集器将 nginx 日志传输到 kafka 里,然后使用流式计算任务(flink、storm、spark 等)实时消费进行统计。
优点:kafka、filebeat、flink 都是比较成熟的组件,系统搭建比较简单。
缺点:数据传输链路过长、外部依赖太多,稳定性相对较差。
本地处理日志:Agent 在 nginx 机器上进行日志解析、计算,然后通过主动上报或开放一个采集端口由采集端拉取两种方式上报到监控中心。
优点:没有日志传输,也不依赖外部系统,而且由于 Nginx 机器本身负载也不高,因此可以分出一部分算力给 Agent 使用,相比方案一可以节省相当一部分后端计算资源。
缺点:没有资源限制功能,如果资源消耗较高可能会影响到 Nginx。
基于插件、lua 脚本之类的监控增强:常见的有 nginx-module-vts、falcon-ngx_metric 之类,可以将 Nginx 内部状态进行导出,可以配合 Open-falcon 或 Prometheus 之类的开源监控。
优点:由于可以直接使用 Nginx 内部变量,因此不需要解析日志,性能最好。
缺点:需要重新编译 Nginx 后才可启用,对 nginx 的侵入性较大,一旦依赖出现问题对 Nginx 将是破坏性的影响;另外部署和更新的成本也较高。
我们过去用的 Nginx 监控就是采用的方案一,缺点也和上面列举的一样,继续在上面做优化只能暂时性缓解而无法从根本上解决稳定性问题。方案三也同样存在稳定性方面的问题,除非性能真的是瓶颈一般不考虑此方案。我们在对比业内主流的几个 nginx 监控方案如 Mtail、夜莺-Agent、Promethus Nginx-Vts-exporter,Mtail 的在性能消耗、稳定性、功能特性等方面表现都是最优的,采用以 Mtail 为核心的新版的简化架构如下,对比老架构减少了传输链路,在减小数据延迟的前提下还节省了不小的系统资源。
出现问题
随着新 Nginx 监控架构的推进, Mtail 的一些问题也逐渐显露了出来。事情就发生在一个普通的周四晚上,白天的时候配合 sa 灰度了公司一个较大的域名所属的 Nginx 集群 20 台左右机器,Mtail 进程的 cpu 大概维持在 60%~70%之间、内存在 60m 左右,性能也还符合预期。结果晚上下班回家洗完手刚准备拿起玩具逗娃,刷刷刷刷 4 条 Mtail 性能报警毫无防备的打乱了我逗娃的节奏,赶紧打开报警机器对应的域名数据一看,监控图线断的简直是惨不忍睹:
登上 Nginx 机器发现进程的 cpu 使用率保持在 130%左右,偶尔还会达到 150%,对比白天的时候翻了一倍。同批部署的其他机器的上报数据虽然没有出现断线,但是 cpu 也接近 110%。查看 Mtail 监控日志,cpu 上涨的时间和业务晚高峰时间一致,看起来像是晚高峰日志量增加导致 Mtail 出现了性能问题。可是根据之前的压测记录,Mtail 一分钟可以处理 100 万日志,该批机器晚高峰日志量也就 50~60 万/分钟左右,按道理说不应该出现这种情况啊。
继续对比白天和晚上的监控结果,发现晚高峰的时候 Mtail 的监控对象个数翻了一倍,因为之前压测的时候着重关注了日志量这个指标,对于监控对象个数只是模拟了一个很简单的场景,难道是 Mtail 的处理性能不只是取决于日志量,还跟监控对象个数有很大关系? 赶紧在隔离环境的机器上使用脚本模拟了 60 万条线上机器的日志,重新测试后发现原本跑完 60 万压测日志都花不到 30 秒的 Mtail,换成线上的日志后需要跑近 3 分钟!
妥妥的性能问题,没别的说的赶紧解决吧。首先比较容易想到的就是从程序启动参数上进行优化,可是 go 不像 java 那样有丰富的启动参数(有不对的地方请评论区指正),在网上搜到一些参数设置了也是没啥效果。尝试用 master 分支的代码 build 了下,结果还是将近 3 分钟,在测试误差范围以内。在 Mtail 的 github 的仓库里翻了翻 issue 列表,最近一次提的性能相关的 issue 还是 17 年的。。。
没别的好办法只能自己上了,尝试着使用 Go 自带的性能分析工具 pprof 生成一下最近 60 秒运行的报告,执行 top10 命令,发现其中一个 Mtail 的方法的耗时比较可疑,根据方法名字看应该是和查找监控对象相关的,累计调用耗时占到了 50%多,很有可能是晚高峰监控对象增多而该方法执行效率不太高最终出现性能问题了。
为了印证想法从 git 上把 Mtail 的源码拉下来简单扫了几眼,不得不说 Google 开源的项目结构还是很工整的,代码的模块化做的很好,比较容易的就理清了程序的运行逻辑,很典型的生产者消费者模型。在定位到相关代码后我更加坚信了自己的想法:果然是写出了个 O(M*N)复杂度的查询!!!按照它的实现逻辑,查找性能是与监控对象个数和监控对象维度成反比的,如果晚高峰监控对象比白天的时候多一倍,那么查询性能就弱一倍!
大晚上提 issure 等官方修复肯定是来不及了,还好 go 的语法比较简单,虽然没有开发 go 的经验也能凭着搜索引擎写写。核心优化的原理就是为每个监控对象计算一个唯一 key,使用 map 存储 key 和监控对象的映射,这样查找的时间复杂度就和监控对象个数没有太大关系了。在通过单元测试后,赶紧把代码打包放到测试环境试了一下,跑同一份线上的日志数据,只花了不到 30 秒, 性能提升了足足 5 倍,反复核对了几遍数据准确性也没有问题。
后续
后面就是找 sa 打了个包给出问题的几台机器替换了下 Mtail。观察了 1 周左右,白天的 cpu 由灰度前的 60%~70%降到了 20%~30%, 晚高峰也从 130%处理不完变成了 60%~70%之间,再也没出现过数据断点的情况。
小插曲
当然也给 Google 官方提了 pr ,经过了几次修改代码风格、补充测试用例后,最终在本周合并进了主分支(https://github.com/google/mtail/pull/478)
反思
方案选型的时候压测场景务必要做好做全,前期准备的越充分后期遇到的问题就越少。
版权声明: 本文为 InfoQ 作者【BUG侦探】的原创文章。
原文链接:【http://xie.infoq.cn/article/faa29428b36247108a0d09c8a】。文章转载请联系作者。
评论