机器内存爆满问题排查

标签: tuicool | 发表时间:2017-04-11 08:00 | 作者:
出处:http://itindex.net/admin/pagedetail

1 背景

两周前广告开屏服务突然503报警不断,先查看了各种业务监控没发现流量等有什么大变化,因为不久之前出过一次机器出问题的情况,马上去查看了机器是不是正常,果然内存几乎涨满了。大概十来分钟内存就会达到90%多,然后进程就重启了,但是从日志来看并没有什么异常情况,好在并没有将机器拖死。当时第一件事就是回滚代码,各种代码回滚到几天前,没什么效果。后来追查当时事发的一些修改改动,定位到是因为一个广告的上线导致,屏蔽该广告之后问题恢复了,但是当天还是没有查出最终问题。当时机器内存情况得截图:

2 复现问题

虽然当时定位到了由于一广告导致,但是看代码逻辑并没有发现有使用大内存的逻辑,线下用这广告尝试重现都没有出现类似情况,于是就尝试列出以下的可能: 1.线下环境跟线上不一致无法复现,用线上机器搭建复制服务环境 2.跟请求参数有关,有一些信息引发了这个问题,尝试用nginx的日志复放复现 3.请求Session信息有关,需要用线上流量复现

2.1 Nginx日志复放

因为大多数请求参数都在nginx日志里边有,除了一些用户信息外,所以就用线上nginx日志的在线下环境进行复现,但是并没有复现那出问题。 为了排除问题1的可能性,找了台线上机器环境基本一模一样,刚开始为了能快速复现问题,将uwsgi的work数调少了一些,发现并没有出现,后来调到一摸一样,还是没有复现。 复放流量用的代码:

def online_request(mypid):  
    print "I am child %d ID :%d" % (os.getpid(),mypid)
    url_file = open('splash.url', 'r')
    for line in url_file:
        url_info = line.split(' ')
        if len(url_info) < 2:
            continue
        method = url_info[0]
        url = splash_domain + url_info[1]
        # print method,url
        if method == 'GET':
            rsp = requests.get(url, cookies=get_cookie())
        elif method == 'POST':
            rsp = requests.post(url, cookies=get_cookie())
        # print rsp.content
    url_file.close()

def multi_process():  
    for i in range(100):
        mypid=os.getpid()
        p = Process(target=online_request, args=(mypid,))
        p.start()

线上环境也没法复现只好到正式服务改代码来进行,在代码中强制加入了出问题广告的处理,返回时再去掉,内存果然涨上来了。因为线上没法直接调试,没法定位具体问题,,所以得想办法在可随便整的环境上复现,把该代码原封不动的拷贝到复制好环境的线上机器上还是没法复现。 因为流量是用nginx日志进行重放,跟线上流量不一样的就是 请求没有带用户信息,所以需要尝试带上用户信息进行。

2.2 复制线上流量

想要尝试带上用户信息,刚开始先复制了一份cookie信息放到请求参数里,发现没法复现,设置cookie代码:

def get_cookie():  
    cookie_dict = {'login_flag': '2b6f49d1c08adcd9df37493771b08592', 'sid_guard': 'a3f4027dfe4889db67641f51e481e682|1480857181|2591988|Tue, 03-Jan-2017 13:12:
49 GMT', 'sid_tt': 'a3f4027dfe4889db67641f51e481e682', 'install_id': '6516548479', '_ga': 'GA1.2.571656648.1479953898', 'sessionid': 'a3f4027dfe4889db67641f51  
e481e682', 'qh[360]': '1', '_ba': 'BA0.2-20161201-51e32-BEhFm05HUdmuYonrlIYz', 'ttreq': '1$c761574f35fbc3e335af742f53a663bc9b4f30be'}  
    cookies = requests.utils.cookiejar_from_dict(cookie_dict)
    return cookies
....
rsp = requests.post(url, cookies=get_cookie())

尝试无果后决定直接复制线上流量来进行复现,想到之前看过有人用gor试过,查了下资料可行,先说下gor的两种尝试: 1.保存出问题是的现场请求文件,gor可以原封不动的保存请求的所有信息 2.直接转发流量 先用方法1去录制了复现问题时的请求信息,然后在用gor进行复放,使用命令如下:

./gor --input-raw ":81" --output-file requests.gor
./gor --input-file requests_0.gor --output-http "192.168.1.95:1230"

没有复现问题,直接在正式服务上启动了一个进程(为了方便调试)来进行,尝试方法2:

./gor --input-raw ":8063|1%" --output-http "192.168.1.95:1230" --output-http "192.168.1.160:1230" --output-file requests.gor

由于复制了1%的流量,虽然问题复现慢了些还是出现了,同时发现一个当时忽略的问题,并不是一个进程打满了所有内存,而是一个进程占用了1%左右内存(正常情况下只占用0.3%左右),由于线上机器进程数比较多才将内存打满了。

3 问题定位

问题复现了,可是无法用复放请求稳定复现,有随机性这个尴尬的场面使得无法调试问题,那只能尝试抓取问题复现时的现场信息了,找到了一个叫meliae的包,可以dump出python的内存使用信息,但是每次dump使用的时间太长,无法在每次请求的时候都dump一次,所以只能在问题复现后,快速dump一次,于是在代码中加了如下逻辑:

if request.GET.get('dump_mem') is not None:  
    from meliae import scanner
    scanner.dump_all_objects('/home/test/log/dump_pack_%s.txt'%request.GET.get('dump_mem'))

顺利拿到了问题出现时以及正常情况下的内存信息,然后用内存信息进行分析,发现了一个异常的dict对象占用50多兆内存,而它引用的字符串等对象占用也很大,有30多万条信息,用一下命令来解析内存dump文件:

from meliae import loader  
#加载dump文件
om = loader.load('/opt/log/dump.txt')  
#计算各Objects的引用关系
om.compute_parents()  
#去掉各对象Instance的_dict_属性
om.collapse_instance_dicts()  
#分析内存占用情况
om.summarize()

正常:

大于1m的dict没有

异常:

大于1m的dict占用一个50多兆内存,item信息说明:

dict(140214267288384 50331928B 1461046refs 1par)  
对象ID   占用字节数  引用数   父对象数

用脚本解析出该对象的一些字符串信息发现是一些pgc作者信息,感觉一头雾水想不出来哪里用加载这些信息。 虽然找到了对象信息,但是还是服务定位代码问题出在哪?用meliae能拿到对象ID以及它的父子对象信息,所以尝试用这些信息去定位: 1.对象ID查找变量信息,尝试未果行不通 2.用对象父信息查找相关信息,work 通过该对象的父父父..信息找到一个对象,定位到了相关代码:

# 获取所有dict对象
epdict = om_error.get_all('dict')  
# 获取其父对象
epdict[0].p  
# 获取指定ID对象
par = om_error.objs.get(140214267232160)

找到了相关对象,然后搜索相关代码定义及使用的地方,顺藤摸瓜在相关调用出打印出堆栈信息,最终定位出了代码的调用逻辑及调用栈情况。

4 最终结论

最终问题是因为某对象的属性信息代码里加载了大量PGC作者相关信息,存储到本地的一个LRUCache占用了很多内存,由于使用python多进程的模式,每个进程都有一份自己的缓存,每个进程多占用了400M内存,100多个进程多占了四五十G内存,撑爆了机器内存。

5 解决方案

找了问题的原因,解决方案很简单,将需要的信息通过服务获取,避免子自身加载过多数据;数据服务已经是现成的,我们只需要接入新服务解决问题了。

6 总结

6.1 排查过程中的问题

问题复现走了很多弯路,说下复现时的一些疑问: 1.线上机器复制环境为什么复现不稳定:

没有group数据库访问权限(囧),中间复现过几次出现内存上涨是因为获取到了缓存数据 2.正式服务环境机器 重放请求时为什么不稳定复现(请求重放时这个请求一直是503也是见鬼了)

6.2 内存问题总结

1.如果能稳定复现的问题要好查很多,用memory_profile或者单步调试就能定位问题

2.对于这种不好复现的问题,最好能取到内存dump信息,分析内存信息来查找问题,过程就类似此文所述,线上环境都可以留这么一个用meliae来dump内存的接口或者零时添加来分析内存问题,这样面对内存保证问题就可以非常直接的分析了

6.3 涉及工具资料

gor:流量复制工具

使用Gor进行测试

meliae:内存分析工具,分析当前内存使用情况

https://pypi.python.org/pypi/meliae http://blog.chinaunix.net/uid-16480950-id-103655.html
memory

profile: 内存分析工具,分析内存增量情况,没法直观看出当前内存使用状况,有时候分析有些不准

https://pypi.python.org/pypi/memoryprofiler

相关 [机器 内存 问题] 推荐:

机器内存爆满问题排查

- - IT瘾-tuicool
两周前广告开屏服务突然503报警不断,先查看了各种业务监控没发现流量等有什么大变化,因为不久之前出过一次机器出问题的情况,马上去查看了机器是不是正常,果然内存几乎涨满了. 大概十来分钟内存就会达到90%多,然后进程就重启了,但是从日志来看并没有什么异常情况,好在并没有将机器拖死. 当时第一件事就是回滚代码,各种代码回滚到几天前,没什么效果.

解决php内存泄露问题

- - 鲁塔弗的博客
这是08年写的一份文档,我当时在一家网站刚接手做技术负责人,网站每天大概有60万ip/300万pv的访问,网站产品很复杂,代码结构差,开发工程师来来去去,代码只能只读了. 突然有一天开始频繁出现php-fpm进程耗光内存和cpu占有率飙升,前端频繁出现504错误. php-fpm进程耗光内存 这个就是传说中的内存泄露,所谓内存泄露,是指进程在运行过程中,内存占用率逐步上升而不释放,导致系统可用内存越来越少的情况.

(转)ThreadLocal的内存泄漏问题

- - 编程语言 - ITeye博客
原文:http://www.godiscoder.com/?p=479. 在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题. 表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出. 开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放.

Java常见问题分析(内存溢出、内存泄露、线程阻塞等)

- - Java - 编程语言 - ITeye博客
Java垃圾回收机制(GC) . 堆内存3代分布(年轻代、老年代、持久代) . ML(内存泄露) OOM(内存溢出)问题现象及分析 . IBM DUMP分析工具使用介绍. Java应用CPU、线程问题分析. Java垃圾回收机制(GC). 1.GC机制作用 . 1.1 JVM自动检测和释放不再使用的对象内存 .

Firefox 7将解决内存占用问题

- Great Han - Solidot
Firefox 6将增加大量HTML5和CSS3特性,而Firefox 7则集中在内存管理和性能改进上. 内存足迹与循环收集(cycle collection)和垃圾收集(garbage collection)有关:循环收集是循环使用网页DOM对象,而垃圾收集是清空旧的无用的JavaScript对象.

因集成GPU问题 W7 SP1内存检测较少

- Sun - cnBeta.COM
据国外媒体报道,微软公司对Win7 Service Pack 1(SP1)系统的用户会碰到系统对RAM的检测不准确的问题做了确认,并透露Windows Server 2008 R2 SP1也有受到影响. 据微软消息,该问题是由带集成图形处理单元(GPU)的芯片组引起. 由于自身没附带RAM,这种芯片组需要预留安装的内存部分,并分配到GPU的集成组件.

(转)Java中字符串与内存泄漏的问题

- - jackyrong
对于这个写法,实际上对于oldStr是一个char[]数组[h,e,l,l,0,,,c,l,a,r,k],对于subString操作,newStr并不是自己copy oldStr的char[]数组hello自己去创建一个新的char[]数组,而是java在背后进行了String Reusing Optimization,它不会自己创建一个新的char数组,而是reuse原来的char数组.

Java内存管理问题案例分享

- - BlueDavy之技术Blog
在这个slide中分享了Java内存管理常见的三类问题(OOM、Full GC频繁、CMS GC Promotion failed || Concurrent mode failure)的case,以及通常的解决方法.

Protobuf使用不当导致的程序内存上涨问题

- - 百度质量部 | 软件测试 | 测试技术 | 百度测试|QA
作者:祝兴昌   百度质量部. protocol buffers[1]是google提供的一种将结构化数据进行序列化和反序列化的方法,其优点是语言中立,平台中立,可扩展性好,目前在google内部大量用于数据存储,通讯协议等方面. PB在功能上类似XML,但是序列化后的数据更小,解析更快,使用上更简单.

ios 编程日记 :内存问题汇总

- - CSDN博客移动开发推荐文章
(1)UIView本身占用的内存并不是很大,但是使用这个方法(tempImage过大会占用很高的内存). setBackgroundColor:[UIColor colorWithPatternImage:tempImage],这个怎么解决呢. 推荐使用UIImageView,然后再setImage这个方法,这样内存基本不会增长.