前段时间,我们线上的Sentry监控系统出了一次故障,整个过程惊心动魄,从发现问题到定位原因再到解决,花了大半天时间,也暴露了我们在监控系统运维上的很多问题。
Sentry是我们一直在用的前端错误监控系统,线上的几个主要项目,都接入了Sentry,用来收集前端的JS错误、性能数据、用户行为等,是我们排查线上问题的重要工具。平时用得好好的,也没出过什么大问题,没想到那天突然出了故障,而且影响还不小。
今天来做一次完整的故障复盘,从故障发生、发现、定位、解决,到事后的反思和改进,详细记录整个过程,希望能给用Sentry做前端监控的朋友一些参考,避免踩类似的坑。
一、故障发生
那天是周三,本来是很平常的一天,上午大家都在正常开发,没人注意到Sentry有什么问题。
大概上午十点多的时候,有个同事说,他刚才在测试环境复现了一个bug,按照以前的经验,Sentry应该很快就能收到错误报告,但他等了十几分钟,Sentry里还是没有看到这个错误,问我是不是Sentry出问题了。
我当时没太在意,觉得可能是网络延迟,或者是他的测试环境没配置好,让他再等等,或者检查一下配置。但过了一会儿,又有几个同事说,他们也遇到了同样的问题,线上的错误,Sentry里也收不到了,这时候我才意识到,可能真的是Sentry出问题了。
我赶紧登录Sentry的后台,一看,坏了,Sentry的仪表盘显示,最近一个小时,错误数量几乎为零,而平时这个时间点,每小时至少有几十个错误。这明显不正常,说明Sentry确实收不到错误了,监控系统挂了。
更糟糕的是,我尝试在Sentry后台操作,发现页面加载很慢,很多功能点不开,有时候还报502错误,整个Sentry系统都处于半瘫痪状态。
这时候,我开始紧张了,Sentry是我们重要的监控工具,如果它挂了,线上出了问题我们都不知道,不能及时发现和处理,影响会很大。我赶紧拉了个群,把运维和几个相关的同事拉进来,开始排查问题。
二、初步排查
首先,我检查了Sentry的服务状态。我们的Sentry是自己部署的,用Docker部署在一台服务器上,不是用的Sentry官方的云服务。我登录到服务器上,看了一下Docker容器的状态,发现Sentry的几个容器,web、worker、cron、postgres、redis、kafka这些,都在运行,没有明显的崩溃。
但是,看容器的资源占用,发现postgres数据库的CPU和内存占用都很高,CPU一直在90%以上,内存也用了80%多,而且IO也很高,磁盘一直在读写。这说明数据库压力很大,可能是数据库出了问题,导致整个Sentry系统变慢甚至不可用。
然后,我看了一下Sentry的web容器日志,发现大量的数据库超时错误,很多请求因为数据库查询超时,返回了500或者502。这就解释了为什么Sentry后台页面加载慢,很多功能用不了,因为数据库压力太大,查询超时了。
那为什么数据库压力突然变大了呢?我看了一下数据库的连接数,发现连接数很高,几乎打满了,很多连接处于空闲状态,或者是在等待锁。这说明可能有慢查询,或者是锁等待,导致数据库连接被占满,新的请求无法处理。
我又看了一下Sentry的worker容器日志,发现worker在大量地处理事件,队列里积压了很多事件,处理不过来。Sentry的架构是,前端上报的错误事件,先发到kafka队列里,然后worker从队列里取事件,处理之后存到postgres数据库里。如果worker处理不过来,事件就会在队列里积压,而worker处理事件的时候,需要大量写数据库,也会给数据库造成很大的压力。
到这里,初步的判断是:Sentry的事件处理出现了积压,worker处理不过来,大量的事件写入导致数据库压力过大,数据库查询超时,进而导致整个Sentry系统变慢甚至不可用,新的事件也无法正常处理和存储,所以我们收不到错误报告了。
但问题是,为什么事件会突然积压?平时都好好的,为什么今天突然处理不过来了?是突然有大量的错误上报,还是worker出了问题,还是数据库性能下降了?需要进一步排查。
三、定位原因
我们开始进一步排查,首先看了一下kafka队列的积压情况。Sentry用kafka做事件队列,我们看了一下kafka的consumer lag,发现积压了几十万条事件,而且还在快速增长,说明worker的处理速度远远跟不上事件的上报速度。
然后,我们看了一下事件的上报量,发现今天的事件上报量,比平时高了好几倍,平时每天大概几万条事件,今天一上午就已经十几万条了,而且还在增长。这就奇怪了,为什么今天的事件量突然暴涨?
我们分析了一下事件的内容,发现大部分事件,都是来自同一个项目,而且都是同一种错误,是一个JS的TypeError,错误信息是"Cannot read property 'xxx' of undefined",而且这个错误的发生频率非常高,几乎每秒都有好几次。
我们赶紧联系了这个项目的负责人,问他们是不是最近上线了什么版本,或者有什么活动。负责人说,他们昨天晚上上线了一个新版本,今天上午有个运营活动,流量比平时大很多。我们看了一下那个错误,发现是新版本里的一个bug,在某个特定场景下,会访问一个undefined对象的属性,导致报错。
这个bug本身不是什么大问题,但问题是,这个错误发生的频率太高了,因为运营活动流量大,很多用户都会触发这个错误,而Sentry默认会把每一个错误都上报,所以瞬间就有大量的错误事件涌进来,把Sentry给冲垮了。
而且,更糟糕的是,这个项目的Sentry配置,没有设置采样率,也没有设置错误去重,每一个用户的每一次错误,都会作为一个独立的事件上报到Sentry,导致事件量暴涨。平时流量小的时候,还看不出来,一到大流量或者有高频错误的时候,事件量就会爆炸,把Sentry冲垮。
到这里,原因就找到了:某个项目昨天晚上上线了新版本,有一个bug,今天上午运营活动流量大,大量用户触发这个bug,产生了海量的错误事件,而这个项目的Sentry配置没有采样和去重,所有事件都上报,导致Sentry的事件队列积压,worker处理不过来,大量写入把数据库压垮,整个Sentry系统变慢甚至不可用,新的事件也无法正常处理。
原因找到了,但问题还没解决,Sentry还处于半瘫痪状态,需要赶紧处理,恢复服务。
四、紧急处理
我们分几步来紧急处理:
第一步:先止住血,减少事件上报
当务之急,是先减少事件的上报量,不让更多的事件涌进来,不然Sentry永远恢复不了。
我们做了两个操作:
- 让那个项目的负责人,赶紧修复那个bug,先热更新上线,止住错误的产生。负责人很快定位了问题,改了代码,热更新上线,大概半小时之后,那个高频错误就不再产生了。
- 我们在Sentry的接入层,临时加了一个限流,对事件上报做了限流,超过一定速率的事件直接丢弃,防止更多的事件涌进来。同时,把那个项目的采样率临时调到了10%,只上报10%的事件,减少上报量。
这两步做完之后,事件的上报量很快就降下来了,不再有新的海量事件涌进来,算是止住血了。
第二步:清理积压,恢复服务
止住血之后,接下来要处理已经积压的事件,让Sentry恢复正常。
当时kafka里积压了几十万条事件,如果让worker慢慢处理,可能要好几个小时,而且处理这些积压事件的时候,还是会大量写数据库,数据库压力还是很大,Sentry还是会很慢。
我们讨论了一下,决定先把积压的事件清理掉一部分,只保留最近的一部分,旧的直接丢弃。因为这些积压的事件,大部分都是那个高频错误,已经没有太大的分析价值了,而且我们的首要目标是恢复Sentry的服务,让它能正常接收和处理新的事件,而不是把所有积压的事件都处理完。
我们操作了kafka,把那个topic里的旧消息清理掉,只保留最近的一万条左右。清理完之后,积压一下子就少了很多,worker很快就把剩下的处理完了。
然后,我们重启了Sentry的几个服务,web、worker、cron都重启了一遍,让它们恢复正常状态。重启之后,Sentry的后台页面加载速度恢复了正常,功能也都能用了,数据库的CPU和内存占用也降下来了,连接数也恢复了正常。
我们测试了一下,主动触发了一个错误,Sentry很快就收到了,大概几秒钟就出现在后台了,说明Sentry已经恢复正常了。
第三步:验证和观察
恢复之后,我们没有掉以轻心,继续观察了一段时间,看Sentry是不是稳定,有没有再出问题。同时,也检查了一下各个项目的事件上报是不是正常,有没有遗漏重要的错误。
观察了大概一个小时,Sentry运行稳定,事件上报和处理都正常,数据库资源占用也正常,没有再出现积压和超时的情况,我们才松了一口气,这次故障算是解决了。
从发现问题到完全恢复,大概花了四个多小时,整个过程还是很惊心动魄的,尤其是刚开始不知道原因的时候,很担心Sentry恢复不了,影响线上问题的排查。好在最后定位到了原因,也顺利恢复了。
五、故障暴露的问题
这次故障,虽然最终解决了,但也暴露了我们在Sentry监控系统运维上的很多问题,值得好好反思。
1. 没有容量规划和限流机制
这是最核心的问题。我们的Sentry是自己部署的,服务器的配置是固定的,能处理的事件量是有限的,但我们没有做容量规划,也不知道Sentry的处理上限是多少,更没有限流机制。一旦有突发的大量事件涌进来,就会把Sentry冲垮,完全没有防护能力。
而且,我们也没有监控Sentry本身的运行状态,比如队列积压、worker处理速度、数据库负载这些,都是出了问题之后,人工去查才发现的,没有告警,不能提前发现问题,及时处理。如果有监控和告警,在事件开始积压的时候,就能收到告警,提前处理,可能就不会发展到整个系统瘫痪的程度。
2. 项目接入Sentry没有规范
各个项目接入Sentry的时候,没有统一的规范,都是各搞各的,有的项目设置了采样率,有的没有,有的做了错误去重,有的没有。出问题的那个项目,就是没有设置采样率,也没有做去重,所有错误都上报,一遇到高频错误,事件量就爆炸了。
而且,项目上线的时候,也没有检查Sentry的配置是不是合理,有没有可能产生大量错误的风险,完全是放养状态。
3. 对Sentry的架构和性能了解不够
说实话,在这次故障之前,我们对Sentry的架构和性能了解得并不深入,只知道怎么用,怎么接入,怎么看错误,但对它的内部架构,比如kafka队列、worker处理、数据库存储这些,了解得不多,也不知道它的性能瓶颈在哪里,能承受多大的事件量。
出了问题之后,我们才去研究Sentry的架构,才知道它的处理流程,才知道怎么排查问题,怎么优化。如果平时就对Sentry有深入的了解,做好运维和优化,可能就不会出这次故障,或者出了问题也能更快地解决。
4. 没有应急预案
这次故障,我们是临时排查,临时想解决方案,没有应急预案,也没有操作手册,走了不少弯路。比如,刚开始的时候,不知道怎么清理kafka的积压,查了半天资料才弄明白,浪费了不少时间。如果有应急预案和操作手册,遇到类似的问题,就能按照预案快速处理,节省时间,减少影响。
5. 太依赖Sentry,没有备用方案
Sentry挂了之后,我们就完全看不到前端错误了,没有其他的备用监控手段,只能干着急。这说明我们太依赖Sentry了,没有备用方案,一旦Sentry出问题,前端监控就完全失明了。
其实,除了Sentry,我们还可以有其他的监控手段,比如自己做一个简单的错误上报,或者用其他的监控工具做备份,至少在Sentry挂了的时候,还能看到关键的错误,不会完全失明。
六、后续改进措施
针对这些问题,我们做了一系列的改进措施,避免以后再出类似的问题。
1. 做容量规划,加监控和告警
我们对Sentry做了容量规划,测试了当前服务器配置下,Sentry的处理上限是多少,能承受多大的事件量,然后根据这个上限,设置了告警阈值。
同时,我们给Sentry本身加了监控,监控的指标包括:
- kafka队列的积压量,超过阈值就告警
- worker的处理速度和处理延迟
- 数据库的CPU、内存、IO、连接数
- Sentry web服务的响应时间和错误率
- 各个项目的事件上报量,突然暴涨就告警
有了这些监控和告警,Sentry一旦出现异常,我们就能第一时间收到告警,及时处理,不会等到整个系统瘫痪了才发现。
2. 加限流和降级机制
我们在Sentry的接入层加了限流机制,对每个项目的事件上报做限流,超过一定速率的事件直接丢弃,防止单个项目的海量事件把整个Sentry冲垮。
同时,也加了降级机制,当Sentry的负载过高的时候,自动降低采样率,只采样一部分事件,保证核心的错误能上报,非核心的错误可以丢弃,优先保证Sentry的可用性。
3. 制定Sentry接入规范
我们制定了统一的Sentry接入规范,要求所有项目接入Sentry的时候,必须遵守:
- 必须设置合理的采样率,根据项目的流量大小设置,流量大的项目采样率要低一些,避免事件量过大
- 必须做错误去重,相同的错误不要重复上报,或者合并上报
- 必须配置合理的release和环境,方便区分不同版本和环境的错误
- 上线前必须检查Sentry配置,确认没有问题
- 高频错误要做过滤,不要把已知的、不影响业务的错误都上报
规范制定之后,我们对所有已接入的项目做了一次排查,不符合规范的都要求整改,避免再出类似的问题。
4. 深入研究Sentry,做性能优化
我们安排了专人,深入研究Sentry的架构和性能,做了一系列的优化:
- 对postgres数据库做了优化,调整了参数,加了索引,清理了旧数据,提高了查询性能
- 对worker做了优化,调整了worker的数量和并发数,提高了事件处理速度
- 对kafka做了优化,调整了分区数和副本数,提高了队列的处理能力
- 清理了Sentry里的旧数据,只保留最近90天的,减少数据库的数据量,提高性能
- 考虑后续升级Sentry的版本,或者把Sentry迁移到配置更高的服务器上,提高处理能力
通过这些优化,Sentry的处理能力提高了不少,稳定性也增强了。
5. 制定应急预案和操作手册
我们针对Sentry可能出现的故障,制定了应急预案和操作手册,包括:
- 事件积压的处理流程
- 数据库压力过大的处理流程
- 服务不可用的处理流程
- 数据备份和恢复的流程
- 各种常用操作的命令和步骤
有了应急预案和操作手册,以后再出类似的问题,就能按照预案快速处理,不用临时想方案,节省时间,减少影响。
6. 建设备用监控方案
我们不再完全依赖Sentry,开始建设备用的前端监控方案,做了一个轻量级的错误上报服务,只收集关键的、高优先级的错误,作为Sentry的备份。当Sentry出问题的时候,至少还能通过这个备用服务,看到关键的错误,不会完全失明。
同时,我们也在考虑,后续可以引入其他的监控工具,和Sentry互补,提高前端监控的可靠性。
七、经验和教训
这次故障,给我们带来了很多经验和教训,也分享给大家。
1. 监控系统本身也需要监控
这是最重要的教训。我们用Sentry监控前端应用,但Sentry本身也是一个系统,也会出问题,也需要监控。很多人容易忽略这一点,觉得监控系统就是用来监控别人的,自己不会出问题,或者出了问题也没关系。但实际上,监控系统出了问题,影响可能更大,因为你会失去对线上系统的可见性,出了问题都不知道。
所以,一定要给监控系统本身也加监控和告警,监控它的运行状态、性能、队列积压、错误率这些,一旦出现异常,及时告警,及时处理。不要等到监控系统完全瘫痪了,才发现它出了问题。
2. 任何系统都要有容量规划和限流
不管是什么系统,只要是对外提供服务的,都要有容量规划,知道自己的处理上限是多少,也要有限流机制,防止突发的大流量把系统冲垮。Sentry是这样,其他系统也是这样。
很多人做系统的时候,只考虑功能实现,不考虑容量和限流,觉得平时流量不大,不会出问题。但互联网时代,什么情况都可能发生,一个运营活动,一个bug,一个热点事件,都可能带来突发的大流量,如果没有限流,系统很容易被冲垮。所以,限流是系统的基本防护,一定要有。
3. 不要忽视小问题,小问题可能引发大故障
这次故障的起因,只是一个小小的JS bug,访问了undefined对象的属性,本身不是什么大问题,也不影响核心业务。但就是这个小bug,在大流量的情况下,产生了海量的错误事件,把Sentry冲垮了,引发了大故障。
所以,不要忽视小问题,任何小问题,在特定的条件下,都可能引发大故障。上线前要做好测试,尽量避免bug上线,尤其是高频触发的bug。同时,也要做好容错和防护,即使有bug,也不要让它影响到其他系统。
4. 运维能力很重要,不能只重开发不重运维
这次故障,也暴露了我们运维能力的不足。我们平时更重视开发,对运维不够重视,Sentry部署上去之后,就很少管了,没有做持续的运维和优化,也没有深入了解它的架构和性能,出了问题才临时抱佛脚。
现在的系统,越来越复杂,运维能力越来越重要,不能只重开发不重运维。系统上线之后,要持续地监控、维护、优化,深入了解系统的架构和性能,做好容量规划、应急预案、备份恢复这些,才能保证系统稳定运行。
5. 不要把所有鸡蛋放在一个篮子里
太依赖Sentry,没有备用方案,Sentry挂了就完全失明,这也是一个教训。任何系统,都可能出问题,不要把所有希望都寄托在一个系统上,要有备用方案,要有冗余,这样即使一个系统出了问题,还有其他的可以用,不会完全瘫痪。
这个道理,不仅适用于监控系统,也适用于其他系统,比如数据库、缓存、消息队列这些,都要有备份和冗余,不要单点依赖。
八、写在最后
这次Sentry故障,虽然过程惊心动魄,也影响了大半天的工作,但也给我们上了生动的一课,让我们意识到了监控系统运维的重要性,也推动我们做了很多改进,从长远来看,是有价值的。
故障复盘,不是为了追究谁的责任,而是为了从故障中学习,找到问题,改进问题,避免以后再出类似的故障。每一次故障,都是一次成长的机会,只要我们认真复盘,持续改进,系统就会越来越稳定,越来越可靠。
最后,希望这篇故障复盘,能给用Sentry做前端监控的朋友一些参考,也希望大家都能重视监控系统的运维,做好监控和告警,做好容量规划和限流,避免踩类似的坑。也欢迎大家在评论区分享自己遇到过的监控系统故障,一起交流学习。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录