前几天线上出了个Go微服务框架的Bug,折腾了我一整夜才定位和解决,今天记录一下排查过程和经验教训。
前几天,线上,突然,出,了,一个,奇怪,的,故障,我们,用Go写,的,一个,微服务,框架,在,没有,任何,代码,变更,的,情况,下,突然,出现,了,大量,的,请求,超时,和,错误,而且,现象,非常,诡异,不是,所有,请求,都,出错,而是,随机,的,一部分,请求,出错,有时候,正常,有时候,不正常,而且,重启,服务,之后,会,好,一阵,子,但是,过,一段,时间,又,会,复现,非常,奇怪,这个,故障,导致,线上,很多,用户,受,影响,报警,一直,响,领导,也,很,重视,让,我,负责,排查,和,解决,我,从,晚上,八九点,开始,排查,一直,折腾,到,第二天,凌晨,四五点,才,终于,定位,到,问题,根源,并且,解决,了,整整,熬,了,一夜,非常,辛苦,但是,也,学到,了,很多,今天,这篇,文章,就,来,记录,一下,这次,排查,的,过程,以及,经验教训,和,大家,分享,一下,希望,能,给,做,Go,微服务,开发,的,朋友,一些,参考,避免,踩,同样,的,坑。
一、故障现象和初步排查
先,说说,故障,现象,那天,晚上,大概,八点多,我们,的,监控,系统,突然,开始,报警,说,某个,核心,微服务,的,错误率,升高,请求,超时,变多,我,第一,时间,登录,监控,系统,看,了,一下,发现,这个,服务,的,P99,响应,时间,突然,从,平时,的,几十毫秒,飙升,到,了,几秒,甚至,十几秒,错误率,也,从,平时,的,不到,百分之一,飙升,到,了,百分之二三十,而且,现象,是,阵发性,的,有时候,正常,有时候,突然,飙升,过,一会,又,好,一点,然后,又,飙升,非常,诡异。
我,首先,做,了,几个,初步,排查,第一,看,最近,有没有,代码,发布,或者,配置,变更,查,了,发布,记录,和,配置,变更,记录,最近,几天,都,没有,任何,变更,代码,和,配置,都,是,好,几天,前,的,一直,运行,正常,所以,排除,了,变更,导致,的,问题,第二,看,服务器,的,资源,使用,情况,CPU,内存,网络,IO,等等,发现,CPU,使用率,平时,大概,百分之三四十,故障,的,时候,也,没有,明显,升高,还是,百分之四五十,内存,也,正常,没有,内存,泄漏,的,迹象,网络,IO,也,正常,没有,带宽,打满,的,情况,所以,排除,了,服务器,资源,瓶颈,导致,的,问题,第三,看,依赖,的,下游,服务,和,中间件,的,状态,比如,数据库,缓存,消息队列,其他,微服务,等等,发现,这些,依赖,都,正常,没有,故障,响应,时间,也,都,正常,所以,排除,了,下游,依赖,导致,的,问题,第四,尝试,重启,了,服务,重启,之后,确实,好,了,一阵,子,错误率,降,下来,了,响应,时间,也,恢复,正常,但是,过,了,大概,半个多小时,又,开始,出现,同样,的,问题,所以,重启,只能,暂时,缓解,不能,根本,解决,问题。
这,一下,我,就,有点,懵,了,没有,变更,资源,正常,下游,正常,但是,服务,就是,随机,出,问题,重启,能,暂时,好,但是,过,一会,又,复现,这种,问题,最,难,排查,了,因为,没有,明显,的,线索,我,只能,继续,深入,排查,看,日志,看,链路,追踪,看,更,详细,的,监控,数据,一点点,找,线索。
二、深入排查,寻找线索
初步,排查,没有,找到,明显,原因,我,开始,做,更,深入,的,排查,主要,从,几个,方面,入手。
第一,详细,看,错误,日志,和,慢请求,日志,我,把,故障,时间段,的,错误,日志,和,慢请求,日志,都,捞,出来,仔细,看,发现,出错,的,请求,类型,很,分散,不是,某个,特定,接口,出错,而是,各种,接口,都,有,出错,的,而且,出错,的,错误,信息,也,不一样,有的,是,超时,有的,是,连接,被,重置,有的,是,context canceled,各种,错误,都,有,这,说明,问题,不是,某个,特定,接口,或者,特定,逻辑,的,问题,而是,更,底层,的,问题,比如,框架,层面,或者,网络,层面,的,问题,另外,我,还,发现,一个,细节,就是,出错,的,请求,虽然,类型,分散,但是,好像,有,一定,的,集中性,就是,某一个,时间段,内,会,集中,出,一批,错误,然后,过,一会,又,好,了,然后,又,集中,出,一批,这种,阵发性,的,特征,让,我,怀疑,可能,是,某种,资源,耗尽,或者,锁,竞争,的,问题,比如,连接池,耗尽,或者,goroutine,泄漏,或者,锁,竞争,导致,阻塞,等等。
第二,看,goroutine,数量,和,状态,因为,是Go写,的,服务,goroutine,是,很,重要,的,指标,我,通过,服务,的,pprof,接口,看,了,一下,goroutine,的,数量,和,状态,发现,正常,情况,下,goroutine,数量,大概,是,几百个,但是,故障,的,时候,goroutine,数量,飙升,到,了,几千个,甚至,上万个,而且,很多,goroutine,都,处于,阻塞,状态,这,就,很,可疑,了,goroutine,数量,暴涨,而且,大量,阻塞,说明,有,什么,东西,导致,goroutine,无法,正常,完成,一直,阻塞,在,那里,积累,起来,我,进一步,看,了,goroutine,的,堆栈,信息,发现,大量,阻塞,的,goroutine,都,阻塞,在,同一个,地方,就是,我们,微服务,框架,里,的,一个,获取,连接,的,函数,上,这,一下,就,有,线索,了,原来,大量,的,goroutine,都,在,等,获取,连接,说明,连接池,里,的,连接,不够,用,了,或者,连接,被,卡住,了,无法,释放,导致,新,的,请求,拿,不到,连接,只能,阻塞,等待,等,的,时间,长,了,就,超时,出错,这,也,能,解释,为什么,错误,是,阵发性,的,因为,连接,被,占住,之后,请求,都,阻塞,等,连接,等,到,超时,就,出错,然后,连接,可能,被,释放,一些,又,能,处理,一些,请求,然后,又,被,占住,又,阻塞,这样,就,表现,为,阵发性,的,错误,也,能,解释,为什么,重启,能,暂时,好,因为,重启,之后,连接池,重置,了,连接,都,释放,了,所以,能,正常,处理,请求,但是,过,一会,连接,又,被,占住,就,又,出,问题,了。
第三,排查连接池的问题,找到连接为什么被占住。既然,线索,指向,了,连接池,我,就,开始,深入,排查,连接池,的,问题,我们,的,微服务,框架,里,用,的,是,一个,自研,的,RPC,框架,里面,有,自己,实现,的,连接池,用来,管理,和,下游,服务,的,TCP连接,我,先,看,了,连接池,的,实现,代码,逻辑,看起来,是,正常,的,有,获取,连接,的,方法,有,释放,连接,的,方法,有,最大,连接数,限制,有,空闲,连接,管理,等等,看起来,没,什么,问题,但是,为什么,连接,会,被,占住,不,释放,呢,我,又,看,了,调用,连接池,的,地方,看,获取,连接,之后,有没有,正确,释放,连接,看,了,一圈,发现,大部分,地方,都,有,正确,释放,连接,用,defer,或者,try-finally,的,方式,确保,连接,被,释放,但是,我,发现,有,一个,地方,可能,有,问题,就是,在,处理,请求,的,某个,分支,逻辑,里,如果,发生,了,某种,特定,的,错误,或者,异常,可能,会,跳过,释放,连接,的,代码,导致,连接,没有,被,释放,一直,被,占住,但是,这个,分支,逻辑,平时,很少,触发,所以,一直,没有,暴露,问题,直到,最近,可能,因为,某种,原因,这个,分支,触发,的,频率,变高,了,导致,连接,泄漏,越来越多,最后,连接池,被,耗尽,就,出现,了,故障,但是,我,仔细,看,了,代码,发现,那个,分支,虽然,可能,跳过,释放,连接,的,代码,但是,按理说,应该,也,有,defer,来,兜底,释放,连接,啊,怎么,会,泄漏,呢,我,又,仔细,看,了,半天,终于,发现,了,问题,所在。
三、定位根因,一个隐蔽的Bug
原来,问题,出在,连接池,获取,连接,的,那个,函数,里,有,一个,非常,隐蔽,的,Bug,具体,来说,是,这样,的,我们,的,连接池,获取,连接,的,函数,逻辑,大概,是,这样,的,先,从,空闲,连接,列表,里,找,有没有,可用,的,空闲,连接,如果,有,就,拿,一个,出来,检查,一下,连接,是否,还,有效,如果,有效,就,返回,这个,连接,如果,没有,空闲,连接,或者,空闲,连接,都,失效,了,就,看,当前,连接,数,有没有,超过,最大,连接数,如果,没,超过,就,创建,一个,新,连接,返回,如果,已经,超过,最大,连接数,了,就,阻塞,等待,等,其他,goroutine,释放,连接,之后,再,获取,这个,逻辑,看起来,是,正常,的,对吧,但是,问题,出在,阻塞,等待,的,那,部分,逻辑,里。
具体,来说,阻塞,等待,的,实现,是,用,了,一个,channel,来,做,的,当,连接,数,达到,上限,的,时候,获取,连接,的,函数,会,创建,一个,channel,然后,把,这个,channel,放到,一个,等待,队列,里,然后,阻塞,在,这个,channel,上,等,其他,goroutine,释放,连接,的,时候,会,从,等待,队列,里,取,一个,等待,的,channel,然后,往,里面,发,一个,信号,通知,等待,的,goroutine,连接,已经,释放,可以,获取,了,这个,实现,思路,是,没问题,的,但是,Bug,出在,等待,超时,的,处理,上,因为,获取,连接,不能,无限,等待,所以,有,一个,超时,时间,如果,等待,超过,超时,时间,还,没,拿到,连接,就,返回,超时,错误,这个,超时,的,实现,是,用,select,加,time.After,来,做,的,大概,是,这样,的,代码,select,case,<-waitChan,获取,连接,成功,case,<-time.After(timeout),返回,超时,错误,这个,代码,看起来,也,没问题,对吧,但是,问题,就,出在,这里,当,超时,发生,的,时候,函数,返回,了,超时,错误,但是,那个,waitChan,还,在,等待,队列,里,没有,被,移除,也就是说,这个,goroutine,已经,超时,返回,了,但是,它,的,waitChan,还,留在,等待,队列,里,后面,其他,goroutine,释放,连接,的,时候,会,从,等待,队列,里,取,这个,waitChan,然后,往,里面,发,信号,但是,这时候,原来,等待,的,goroutine,已经,走,了,不在,这个,channel,上,等,了,所以,这个,信号,就,没人,收,而且,因为,这个,channel,是,无缓冲,的,往,里面,发,信号,会,阻塞,所以,释放,连接,的,那个,goroutine,就,会,阻塞,在,往,这个,已经,没人,等,的,channel,发,信号,上,永远,阻塞,住,这样,就,导致,释放,连接,的,goroutine,卡住,了,连接,也,无法,正常,释放,然后,更多,的,goroutine,来,获取,连接,拿不到,就,进入,等待,队列,然后,又,超时,又,留下,更多,的,无效,waitChan,然后,释放,连接,的,时候,又,会,取,到,这些,无效,的,waitChan,又,阻塞,这样,就,形成,了,一个,恶性循环,连接,越来越,多,被,卡住,无法,释放,等待,的,goroutine,越来越,多,最后,整个,服务,就,卡住,了,大量,请求,超时,出错。
这个,Bug,真的,非常,隐蔽,因为,它,需要,满足,几个,条件,才,会,触发,第一,连接池,要,达到,最大,连接数,才,会,进入,等待,逻辑,第二,等待,的,goroutine,要,发生,超时,才,会,留下,无效,的,waitChan,第三,释放,连接,的,时候,要,刚好,取,到,这个,无效,的,waitChan,才,会,阻塞,所以,平时,流量,不,大,的,时候,连接池,很少,达到,上限,也,很少,有,等待,超时,所以,这个,Bug,一直,没有,暴露,但是,那天,晚上,可能,因为,某个,下游,服务,偶尔,变慢,导致,连接,占用,时间,变长,连接池,更容易,达到,上限,等待,超时,也,变多,就,触发,了,这个,Bug,然后,恶性循环,就,出现,了,故障,而且,因为,是,阵发性,的,所以,更,难,排查,我,也是,看,了,很久,代码,又,结合,pprof,的,goroutine,堆栈,信息,才,终于,定位,到,这个,隐蔽,的,Bug,当时,找到,原因,的,时候,已经,凌晨,三四点,了,真的,非常,不容易,但是,也,很,有,成就感。
四、解决问题和验证
找到,根因,之后,解决,就,简单,了,问题,的,核心,是,超时,返回,的,时候,没有,把,waitChan,从,等待,队列,里,移除,导致,后面,释放,连接,的,时候,会,取,到,无效,的,waitChan,造成,阻塞,所以,解决,方法,就是,在,超时,返回,之前,把,自己,的,waitChan,从,等待,队列,里,移除,掉,这样,后面,释放,连接,的,时候,就,不会,取,到,这个,无效,的,waitChan,了,就,不会,阻塞,了,我,改,了,代码,在,超时,的,那个,case,里,加,了,从,等待,队列,移除,当前,waitChan,的,逻辑,因为,等待,队列,是,一个,切片,移除,元素,需要,注意,并发,安全,所以,加,了,锁,保护,确保,移除,操作,是,原子,的,不会,和,其他,goroutine,的,入队,出队,操作,冲突,改,完,代码,之后,我,又,仔细,review,了,几遍,确认,逻辑,没问题,也,没有,引入,新的,并发,问题,然后,写,了,一个,单元测试,模拟,连接池,满,等待,超时,的,场景,验证,修复,之后,不会,再,出现,goroutine,阻塞,和,连接,泄漏,的,问题,测试,通过,了,说明,修复,是,有效,的。
然后,我,把,修复,的,代码,打包,发布,到,线上,因为,是,框架,层,的,修复,所以,需要,重新,编译,所有,依赖,这个,框架,的,服务,然后,发布,上线,发布,之后,我,继续,观察,了,几个,小时,监控,数据,发现,错误率,和,响应,时间,都,恢复,正常,了,goroutine,数量,也,稳定,在,正常,范围,没有,再,出现,暴涨,和,大量,阻塞,的,情况,连接池,的,连接,数,也,稳定,正常,没有,再,出现,连接,耗尽,的,情况,说明,问题,确实,解决,了,这,时候,已经,凌晨,五点,多,了,天,都,快,亮,了,我,熬,了,整整,一夜,终于,把,问题,解决,了,虽然,很,累,但是,心里,还是,很,踏实,的,也,很,有,成就感。
五、经验教训和总结
这次,排查,线上,Bug,熬,了,一夜,虽然,辛苦,但是,也,学到,了,很多,总结,一下,经验教训,和,大家,分享。
第一,并发代码一定要仔细review,隐蔽的Bug往往出在并发逻辑里。这次,的,Bug,就是,一个,典型,的,并发,逻辑,的,Bug,非常,隐蔽,平时,很难,发现,只有,在,特定,条件,下,才,会,触发,所以,写,并发,代码,的,时候,一定要,非常,小心,仔细,review,考虑,各种,边界,情况,和,异常,分支,比如,超时,取消,错误,等等,这些,分支,往往,最,容易,出,问题,因为,平时,很少,走,到,但是,一旦,走,到,就,可能,出,大,问题,另外,并发,代码,一定要,写,充分,的,单元测试,和,并发,测试,模拟,各种,场景,尤其是,边界,和,异常,场景,确保,代码,在,各种,情况,下,都,能,正确,运行,不会,出现,死锁,泄漏,阻塞,等等,问题。
第二,线上故障排查要善用工具,尤其是pprof这类诊断工具。这次,排查,能,最终,定位,到,问题,pprof,功不可没,通过,pprof,看,goroutine,的,数量,和,堆栈,信息,很快,就,发现,了,大量,goroutine,阻塞,在,连接池,获取,连接,的,地方,给,了,我,很,重要,的,线索,不然,光,看,代码,和,日志,可能,还,要,排查,更久,所以,线上,服务,一定要,开启,pprof,或者,类似,的,诊断,接口,方便,出,问题,的,时候,排查,另外,也要,熟悉,这些,工具,的,使用,知道,怎么,看,goroutine,堆栈,怎么,看,CPU,profile,怎么,看,内存,分配,等等,这些,工具,在,排查,线上,问题,的,时候,非常,有用,能,大大,提高,排查,效率。
第三,排查问题要有耐心,不要轻易放弃,尤其是诡异的问题。这次,的,问题,非常,诡异,没有,变更,资源,正常,下游,正常,阵发性,出现,重启,能,暂时,好,这种,问题,最,容易,让人,崩溃,因为,没有,明显,线索,排查,过程,中,我,也,有,几次,觉得,没,头绪,有点,沮丧,但是,还是,坚持,下来,了,一点点,找,线索,从,日志,到,监控,到,pprof,到,代码,review,一步步,缩小,范围,最终,定位,到,了,根因,所以,排查,线上,问题,一定,要,有,耐心,不要,轻易,放弃,尤其是,这种,诡异,的,问题,往往,藏,着,很深,的,Bug,只要,坚持,排查,总能,找到,线索,和,原因,另外,也,可以,找,同事,一起,排查,多人,一起,看,问题,往往,能,发现,自己,忽略,的,点,提高,排查,效率。
第四,框架层的代码质量要求更高,因为影响面大。这次,的,Bug,出在,微服务,框架,层,所以,影响,的,是,所有,依赖,这个,框架,的,服务,影响面,很,大,而且,因为,是,框架,层,的,问题,排查,起来,也,更,难,因为,大家,一般,不会,怀疑,框架,有,问题,都会,先,查,自己,的,业务,代码,所以,框架,层,的,代码,质量,要求,应该,更,高,要,更,严格,的,review,更,充分,的,测试,更,完善,的,文档,因为,一旦,框架,层出,问题,影响,面,太,大,了,损失,也,会,很,大,所以,做,基础,架构,和,框架,开发,的,同学,一定,要,对,代码,质量,有,更高,的,要求,不能,有,侥幸,心理,觉得,这个,分支,很少,走,到,就,不,认真,处理,往往,就是,这些,很少,走,的,分支,会,出,大,问题。
第五,线上问题解决后要做好复盘和沉淀。这次,问题,解决,之后,我,也,做,了,复盘,整理,了,问题,的,现象,排查,过程,根因,解决,方法,以及,经验教训,分享,给,团队,的,其他,同学,让,大家,都,能,从,这次,问题,中,学习,到,东西,避免,以后,再,犯,类似,的,错误,另外,也,完善,了,框架,的,单元测试,和,并发,测试,补充,了,之前,缺失,的,测试,用例,防止,以后,再,出现,类似,的,回归,问题,还,加,了,一些,监控,指标,比如,连接池,的,等待,队列,长度,等待,超时,次数,等等,方便,以后,出,问题,的,时候,能,更快,发现,和,定位,我,觉得,线上,问题,不可怕,可怕,的,是,同样,的,问题,反复,出现,所以,每次,线上,问题,解决,之后,都,要,做好,复盘,和,沉淀,把,经验,教训,记录,下来,把,测试,和,监控,完善,起来,这样,才能,不断,进步,避免,重复,踩坑。
六、写在最后
这次,线上,Go,微服务,框架,的,Bug,排查,了,整整,一夜,虽然,很,辛苦,但是,收获,也,很,大,不仅,解决,了,线上,问题,也,学到,了,很多,排查,问题,的,方法,和,经验,也,对,并发,编程,和,框架,开发,有,了,更,深入,的,理解,我,觉得,做,技术,就是,这样,不断,遇到,问题,不断,解决,问题,在,这个,过程,中,不断,学习,和,成长,虽然,有时候,很,辛苦,很,崩溃,但是,解决,问题,之后,的,成就感,和,收获,也是,很,大,的,这,大概,就是,做,技术,的,乐趣,之一,吧。
希望,这篇,文章,记录,的,排查,过程,和,经验教训,能,给,做,Go,微服务,开发,的,朋友,一些,参考,和,启发,也,希望,大家,在,写,并发,代码,和,框架,代码,的,时候,能,更,小心,更,认真,避免,踩,类似,的,坑,另外,如果,大家,有,什么,线上,排查,的,经验,和,故事,也,欢迎,在,评论,区,分享,出来,一起,交流,一起,学习,一起,进步,谢谢,大家,的,阅读,也,希望,大家,线上,永远,没有Bug,不用,熬夜,排查,问题。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录