暴涨 下午时测试机连续几次宕机,经检查是土豆的一个进程占满内存所致,现象就是一个进程内存突然暴涨,在短时间内占满4G内存,但是杀掉进程重启,这种现象并不能很快复现,初步推断是土豆的代码出了bug,还好正式线没有问题。
因为不好定位哪里的错误,用了半天的时间才搞定,记录我找bug的过程。
- 不成功的过程:
1、通知土豆的测试同事先不要使用测试机,因为这台机器上还有优酷的代码,不要影响到其他服务。
2、既然正式线没有问题,只是测试环境出现,范围缩小在develop分支中还没合并到master分支中的代码(我们测试线是develop分支,测试没有问题会merge到master分支),查看几遍,不能定位错误(惭愧)。
3、分析tornado的错误日志,没有异常。
4、查看有问题的代码的接口,使用ab做压力测试,没有复现。虽然接口就那几个,但是参数无法遍历到。
5、我们不能重现,只好让测试同事做正常测试,以便可以复现,还好增长的过程有一段的时间,我们可以在这时间里将进程杀掉(supervisord会帮我们重启)。
6、大约几十分钟复现一次,我们需要仔细盯着top的进程列表,一旦复现,快速杀死进程(我们不是运维,没有很多的系统监测工具)。
- 成功的过程:
7、15点34分复现,使用grep命令检索出前10分钟和后1分钟的所有nginx的access日志,大约不到2000条,awk命令拼接出它们的url,重定向保存到一个文件中,在这2000条记录中肯定有一条是可以使问题复现的。
8、写一个简单的Python脚本,帮我们去测试每一个url。但需要人工的干预,需要看top命令的输出(内存是否快速上涨),但2000条记录遍历是不能接收的,怎么办呢,二分法!
9、Python程序,程序接收两个参数:s和e,代表遍历的起始、结束记录索引,程序会遍历s和e之间的所有url。我们的任务是输入start、middle查看top输出,如果复现了,则继续输入start、(start+middle)/2,如果没有复现,则输入middle、(middle+end)/2继续测试,二分法的过程我就不详述了。
import requests
import sys
with open('tmp2.log', 'r') as a:
s = int(sys.argv[1])
t = int(sys.argv[2])
data = a.readlines()
if t > len(data):
t = len(data)
#print data[302]
for i in range(s, t):
# print data[i]
query = data[i].replace('_t_', '_t')
content = requests.get('http://' + query).content
# print content
10、大约试了10次,找到了url,请求本地调试,找到了问题,是使用re模块做正则匹配的时候,一条错误的数据导致一个list类型的变量长度为千万级,将内存写满了。
- 优化:
为的是快速定位错误,很多地方很暴力,还是可以优化的:
1、Python代码中外层加一个循环,我们只要输入yes或no,程序自动做二分去查找,而不用每次输入两个数。
2、可以使用python监测服务器的内存占用情况,自动判断是否是复现了问题,并自动杀死进程,这样就可以做到全自动了。
- 总结:
之所以开始没有想到这个方法,我觉得是对于日志的个数太多的误判,认为2000条记录太多了,一个一个的去测试不现实。而log(n)的复杂度则很好的帮我们解决了这一点,很多时候我们是可以认为log(n)==1的,随着n的增大,log(n)无限接近1,而无限远离n,log(10亿)==30,这也是为什么二叉查找树(红黑树、B树等)、跳跃表、堆等这些数据结构如此高效的原因。
这次更像是二分法的一次实战使用,类似于2000个小球中只有一个重量不同,其他重量相同,查找出不同质量的那个小球。