记一次线上bug排查过程及总结

前言

大家好,我是路由器没有路

在线上运行的系统中,问题的出现是不可避免的。如何快速、准确地排查问题,是每个技术人员都需要掌握的技能。

本文将分享一个线上问题排查的过程和总结,希望对大家有所帮助。

问题表现

我们的商城系统有一个问题:有用户反馈打开商城首页有偶现打不开的情况。

前端查了下反馈说接口超时导致,但是查 cgi 接口进程还在,也无相关报错日志。

假设导致的情况

我们首先对问题进行假设,以便更好地进行排查。我们认为问题可能是由以下几种情况导致的:

  1. 网络异常导致
  2. 底层服务异常导致
  3. 底层服务慢查询导致
  4. 服务查询数据量大,回包给 cgi 有问题导致
  5. 接口有 panic 然后重启导致

小心验证

接下来,我们需要对每一种假设进行验证,以排除不符合实际的情况。

  1. 在网络正常的情况下,自己进商城首页试了下,确实也偶现该问题;接着让运维查了下网络带宽,也是正常的;排除网络异常导致。
  2. 查了下底层服务日志,日志无异常报错,进程也无重启情况,排除服务异常导致。
  3. 运维有工具监控慢查询的情况,有慢查询的话会有统计和告警的,查了下慢查询日志没有对应的慢查询;排除慢查询导致。
  4. 查了下该服务对应的 pl 日志,即框架平台日志(平台会有记录每次请求的包大小情况),看请求的包大小在正常阈值范围内,也无报包太大的错误,排除回包太大导致。
  5. 在日志系统查了下该接口的日志,并没有发现有 panic 或者其它异常的报错信息;此时一筹莫展,想着再看下部署机器上的接口进程是否都正常,仔细看发现有个别机器上的接口服务进程有重启过的情况,问了下同事也没有重启和发布的情况;猜测是代码有 bug 导致,于是再仔细 review 下代码的实现,果真是代码有 bug 导致的。

Bug 分析

通过排查,我们发现问题是由代码 bug 导致的

具体来说,平台框架代码有这么个实现:请求进来时 main goroutine 有一个 defer recover panic 的实现,如下所示:

正常如果接口有 panic 的情况会被框架 recover 掉,然后打印相应的错误日志,即在日志系统可以查到对应的报错信息。

但是在排查问题时,之所以没有查到有对应 panic 的日志的原因是:在 cgi 接口业务代码的一个方法中起了多个 goroutine,即开了并发处理一些业务数据汇总的逻辑,而这些新开的 goroutine 中并没有做 defer recover,也就是说当这些 goroutine 中发生异常 panic 的话,整个接口就会崩溃退出。

而这里的 bug 恰恰就是开多个 goroutine 并发写一个共享对象 map(mapFloorId2Idx) 产生竞争导致的(会报 fatal error: concurrent map writes 的异常信息),然后这里程序会重启的原因是有后台监控进程监控到并拉起。实现代码如下:

大家可以想下为什么框架外层的 main goroutine 捕获不到其它 goroutine 的 panic?

原因很简单,就是 Golang 中的 goroutine 没办法跨协程 recover,从而导致程序崩掉

Golang 在发生 panic 时,是先找到本 goroutine,再在这个 goroutine 里的 defer 函数里看看有没有 recover

解决方案

通过分析,我们找到了问题的根本原因:开多个 goroutine 并发写一个共享对象 map 产生竞争导致的。为了解决这个问题,我们需要做两个方面的工作:

  1. 在多 goroutine 操作共享的对象 map 时上锁
  2. 内部的 goroutine 函数要 defer recover

总结

在排查线上问题时,我们需要做到以下几点:

  • 快速响应:一旦发现问题,需要立即响应,不能拖延;
  • 有条理地排查:需要有一定的排查思路和方法,按照假设进行验证,逐步排除不符合实际的情况;
  • 细心仔细:需要仔细查看日志、代码等信息,不能遗漏任何细节;
  • 总结归纳:需要将排查过程和结果进行总结归纳,以便在以后的工作中能够更好地应对类似问题。

通过本次排查,我们不仅解决了一个具体的问题,更重要的是积累了一定的排查经验和技巧,这对我们今后的工作将会有很大的帮助。

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务