Python 内存泄漏排查与分析

python 内存异常的几种情况

  • 代码内出现循环引用
  • 全局变量数据太多
  • 进程加载大量数据,长久持有不释放

现象

线上生产环境一些 tornado 服务(主要提供rest服务)进程占用内存过大:百兆到1G ,测试环境的同样服务未出现异常。

观察分析

1.确定是否是个别进程的特有现象

在一套环境中部署不同的tornado的服务,排除了tornado和第三方库的可能性。

2.利用linux top 等命令观察异常服务进程内存的增长情况

人工观察有异常内存的服务进程(2~3个小时观测一次),发现其内存占有率并不完全统一,即在进程启用之后,有的进程内存占有过大,有的正常,但是在相当长一段时间之后(一天以后),所有进程内存占有几乎趋于一个稳定的比较大的值。由此可以推断出以下几种情况:

  • 生产环境请求复杂,测试环境和生产环境表现不一致,有可能是对某些api的不合法的请求(非法参数未处理)导致内存占用过大
  • 某些api(单次请求的数据量较大)的并发量太大,同一时间内存占有量过大
  • 隐式的改变了全局变量的值,导致变量数据增大,比如全局的list或者dict数据不断被添加
  • 其他未知情况

对代码进行静态分析和检查, 排除了全局变量和循环引用的问题。

侦测线上api调用前后对进程内存的影响

为了能够非常细致的观测到线上api调用前后的进程内存占用率,在api调用的入口记录了当前进程的内存占用,并对日志以进程的id来区分,由于tornado的单进程特性,也可以用端口号来区分,这样日志的划分更稳定,毕竟进程id是个变数。

1
2
3
4
5
6
7
8
9
def log_memory(self):
import psutil
import os
logger_name = ('app_%s'%self.application.settings.get('port'))
m_logger = loggers.getLogger(logger_name, '/var/log/app_%s.log'%logger_name)
process = psutil.Process(os.getpid())
m_logger.info('%s %s MB'%(self.request.path, process.memory_info()[0]/(1024*1024)))
m_logger.info(self.request.arguments)

psutil 提供了获取进程资源占用如内存,cpu,磁盘,网络等的api。为了能查看api调用的上下文环境,日志中还记录了api的请求参数。

解决

通过的日志分析发现,一个api在某些参数的边界未做处理时,会导致一部分很大的数据被加载到内存中。至此,这个问题的根源终于被找到了。

不过一个疑问是:python的进程内存增大之后,是否会让python解释器认为,该进程使用的峰值内存就是如此而不愿意交换给os呢

三月沙 wechat
扫描关注 wecatch 的公众号