@HUST张友东 work@taobao zyd_com@126.com
分类: C/C++
2012-12-08 14:16:04
12月03日03:50左右,有多台Dataserver(DS)内存占用飙升,如10.246.70.71 dataserver 3,常驻内存突然飙升到21G,并且一直没有释放。
查看dataserver的日志,发现大量的read v2失败 ,返回值主要是-8025(block不存在), -8016(文件被删除或隐藏)。但这些失败信息从0点开始一直很多,而且这两种失败不会导致DS分配很多内存,所以应该跟内存问题没关系。
在3:50左右5分钟内,发现readv2 success的日志很多,主要集中在3:48,3:49,3:50这3分钟内,3分钟的readv2请求超过4w,平均每秒200+,每个文件请求都是同一个文件的0-1M部分,请求来源是imgsrc,每个imgsrc都请求多次。查看被访问的文件,是一个3M+的图片;同时明俨从imgsrc的日志上发现,imgsrc多次从cdn节点接收到该文件请求,最开始几次读取整个文件成功,接下来在读取0-1M的时候就超时了,于是imgsrc重试多次。从dataserver日志上也发现网络队列已经满了,很多请求被丢弃了,由于排队时间较长,很多请求在队列中等待也超时了(5s);还有少量请求dataserver服务完了,但回给imgsrc时,imgsrc本地已经超时了(3s)。
Review了 DS readv2的服务接口,以及tbnet的相关的代码,没有发现可能内存泄露的地方;无计可施,只能尝试在线下复现问题。
在测试环境, 用了7台机器,对同一个1.4M左右的文件使用readv2进行读取,不断加大每个机器上的进程数,加到50个进程时,DS的内存上涨比较明显,第一次测试时,内存涨到48.1%(约4g)时停止客户端,DS占用内存没有下降,一直维持在48%左右。接下来准备用valgrind跑,查是否有内存泄露问题,但valgrind跑起来后,DS服务请求非常慢,每秒服务读请求不到10个,压力上不去,内存基本没涨。
继续压测了几次,观察到如下现象:
把客户端的readv2换成read,现象跟readv2类似; 对readv2里分配、释放的点加log,发现分配和释放是完全正确配对的。把ptmalloc库换成google的tcmalloc,现象也差不多。
向华庭请教了下,发现我们的场景很可能造成这个问题;ptmalloc收缩内存是从top chunk开始,如果与top chunk相邻的chunk不能释放,top chunk以下的chunk都无法释放。
简单的说,在一个64M的内存单元里,假设DS分配先后分配了内存1、2、3、4(假设1、2、3、4代表地址递增的内存区间),然后1、2、3都释放了,但4是一块生命周期很长的内存,一直没有释放,这时1、2、3虽然被DS释放了,但由于4没有释放(top chunk相邻的),glibc不会把它们还给操作系统。
DS在服务读请求时,分配的内存在请求服务完成后都会释放内存,这些内存的生命周期都不会很长。但有些内存,比如LogicBlock,PhysicalBlock等结构,一旦分配,只有block被删除才会释放内存,生命周期很长;所以在服务读请求的过程中,一旦又分配了一块生命周期很长的内存,就会导致前面分配的内存都无法归还给操作系统。
把ptmalloc参数改动下,一旦分配超过DEFAULT_MMAP_THRESHOLD的内存,分配就直接mmap,释放就直接munmap。把如下语句加到main函数的开始处
再压测DS,发现DS的内存也一直升高,但一旦停止客户端或减小压力,内存就会迅速降下来。至此,可以确定DS占用很多内存的确是由于ptmalloc没有归还给操作系统的原因。由于我们一台机器上要跑10+个DS,一旦压力大,出现上述问题,势必影响到所有DS的服务。
进一步测试了jemalloc库,在大压力下,DS内存也会迅速上涨,但压力降下来后,jemalloc会将内存归还给操作系统;简单测试对比了jemalloc和glibc的ptmalloc,发现在多线程环境下,jemalloc的表现更胜一筹,后期我们会考虑在DS上使用jemalloc替代ptmalloc作为内存分配器。