最近一件严重的事情 使用低版本的 go 程序,导致 sql 爆掉,api无法响应用户请求,引发血案。

现象

api 核心组件,fd 大梯度上升,api 不响应。kill -SIGQUIT <process>杀掉程序,大量 goroutine 卡在 database/sql.(*DB).conn

修复的尝试

  1. 重启大法,重启 api 组件。(怀疑代码偶发性 bug,导致 fd 爆了,重启组件,释放 fd)。
  2. 重启 mysql(碰运气debug)
  3. 怀疑被自己打趴下了,一些直接请求 api 组件的请求给导入至一个代理组件上(类似 memcache,缓存数据)。(这个当时稍微解决问题了,但是隔了2小时左右,继续出现事故状况)。

嗯。以上的修复最终都是失败的,没有找到根源问题。 最后,重新调整了一下,需要从根源去解决问题,于是从头开始,利用我们自己的系统来 debug,从最小的一个点进行突破。

我厂一些有意思的组件

X-ReqidX-Log。这两个组件非常有意思,应该可以说能够极大层度上的帮助定位问题,无奈之前都不太会用。

X-Reqid。这个是写在http头里面的一个字段,我们的log系统会在这个请求第一次进入我们组件的时候查看,如果有了,就使用这个值,如果没有,就根据算法计算出一个值赋予给它。随后在我们的每一行 log中,都会打出来。我们可以根据这个X-Reqid来定位出在系统中发生了什么事情。

X-Log。这个也是一个头信息,它是一个 array。里面包含着经过每个组件的时间,这样能够定位出,在每个组件中,这个请求呆了多久。

解决问题

最终通过这些信息发现,就是 api 组件卡住了,需要针对问题进行代码级别的 debug了。kill -SIGQUIT能打印出 grouting 是一个很棒的功能,自己实现的话,也可以用 "net/http/pprof"这个包。看到 grouting 卡在database/sql.(*DB).conn。进入代码看,是一个循环,在等锁,卡住的原因是由于 connection 达到配置的上限。都在等 connection。于是决定将 max_open_connection 配置调高,最终解决问题。

收获

解决问题一定要条理清晰,一定不能使用撞大运的解决方式,要从问题的地方一步一步深入,不要停留在外围,利用好一切已有的系统,深入代码定位问题。