作者:李尊举1.问题最近我们运维同事接到一个在线LB(负载均衡)服务内存告警,运维同事反映内存使用情况LB集群中有的机器超过80%,有的甚至超过90%,而且内存占用还在增长。接到内存报警的消息,整个团队都相当紧张。我们团队负责的LB服务是零售、物流、科技等商业服务的流量入口。承担着上万个服务的流量转发。一旦出现故障,将会影响到更多的业务服务,我们必须立即着手解决内存爆炸的问题。目前只是内存告警,暂时不影响业务。首先将内存使用率超过90%的LB服务下线,防止LB服务因内存过大而崩溃,影响业务。运维同仁密切关注相关内存告警消息。2、排查过程,开发结束后,同学们通过cat/proc/meminfo查看Slab的内核内存可能泄漏。$cat/proc/meminfoMemTotal:65922868kBMemFree:9001452kB...slab:39242216kBSReclaimable:38506072kBSUnreclaim:736144kB...通过slabtop命令分析slab发现内核中dentry对象的比例较高,考虑到dentry对象与文件有关,Linux中的一切都可以是文件。这可能与套接字文件有关。进一步排查,发现LB服务上有curl发送的HTTPS检测脚本。这个脚本有一个dentry对象泄露,在curl论坛上找了一篇文章来证实。这个问题,本文说明curl-7.19.7版本发送HTTPS请求时,curl依赖的NSS库存在dentryleakbug。检查了一下我们的curl版本是7.19.7,问题终于暴露了!!!$curl-Vcurl7.19.7(x86_64-redhat-linux-gnu)libcurl/7.19.7NSS/3.15.3zlib/1.2.3libidn/1.18libssh2/1.4.2协议:tftpftptelnetdictldapldapshttp文件httpsftpsscpsftpFeatures:GSS-协商IDNIPv6大文件NTLMSSLlibz$rpm-aq|grepnss-nss-util-3.16.1-3.el6.x86_64nss-sysinit-3.16.1-14.el6.x86_64nss-softokn-freebl-3.14.3-17.el6.x86_64nss-softokn-3.14.3-17.el6.x86_64nss-3.16.1-14.el6.x86_64nss-tools-3.16.1-14.el6.x86_64文章介绍环境变量可以设置NSS_SDB_USE_CACHE修复了这个错误,我们验证了这个解决方案。三、解决方案1、目前先停止检测脚本,在业务流量低的时候,通过drop_caches清除内存使用率超过90%的服务的缓存。2、升级后在检测脚本中设置环境变量NSS_SDB_USE_CACHE,彻底修复该问题。4.回顾总结造成这个内存暴增问题的根本原因是curl-7.19.7依赖的NSS库中的dentryleakbug。检测脚本只暴露了这个问题。这次的问题是Linux内存泄漏引起的,所以重新系统学习一下Linux内存管理的知识是非常有必要的,对我们以后排查内存暴涨的问题会有很大的帮助。1)Linux内存寻址Linux内核主要通过虚拟内存来管理进程的地址空间。内核进程和用户进程都只会分配虚拟内存,不会分配物理内存。虚拟内存和物理内存通过内存寻址进行映射。Linux内核中存在三种地址,a。逻辑地址。每个逻辑地址由一个段和一个偏移量组成。偏移量表示从段的开始到实际地址的距离。b.线性地址,也称为虚拟地址,是一个32位无符号整数。32位机器的内存最大为4GB。通常用十六进制数表示。Linux进程的内存一般指的就是这块内存。C。物理地址用于寻址内存芯片级内存单元。它们对应于从CPU地址引脚发送到内存总线的电信号。Linux中的内存控制单元(MMU)通过称为分段单元的硬件电路将逻辑地址转换为线性地址,然后,称为分页单元的第二硬件电路将地址转换为线性地址。线性地址被翻译成物理地址。2)Linux分页机制分页单元将线性地址转换为物理地址。线性地址被分成固定长度的组,称为页。页内的连续线性地址映射到连续物理地址。通常,“页”既指一组线性地址,也指这组地址中包含的数据。分页单元将所有RAM分成固定长度的页框,也称为物理页。每个页框包含一个页(page),也就是说一个页框的长度与一页的长度相同。页框是主存的一部分,因此也是一个存储区。区分页面和页面框架很重要。前者只是一个数据块,可以存放在任何页框或磁盘中。将线性地址映射到物理地址的数据结构称为页表。页表存储在主内存中,并且必须在启用分页单元之前由内核正确初始化。x86_64的Linux内核采用4级分页模型,一般一个page为4K,页表有4种:a、页全局目录b、页上层目录c、页中间目录d、页表pageglobal目录包含若干页上层目录,页上层目录依次包含若干页中间目录的地址,页中间目录包含若干页表的地址。每个页表条目都指向一个页框。线性地址分为5个部分。3)NUMA架构随着CPU进入多核时代,多核CPU通过数据总线访问内存有很大的延迟,于是NUMA架构应运而生.NUMA架构的全称是NonUniformMemoryArchitecture(非统一内存架构)。系统的物理内存被划分为若干个节点(nodes),每个节点绑定不同的CPUcore,本地CPUcore直接访问本地内存节点,延迟最小。可以使用lscpu命令查看NUMA和CPU核心之间的关系。$lscpuArchitecture:x86_64CPUop-mode(s):32-bit,64-bitByteOrder:LittleEndianCPU(s):32On-lineCPU(s)list:0-31Thread(s)percore:2Core(s)persocket:8套接字:2NUMA节点:2供应商ID:GenuineIntelCPU系列:6型号:62步进:4CPUMHz:2001.000BogoMIPS:3999.43虚拟化:VT-xL1d缓存:32KL1i缓存:32KL2缓存:256KL20节点:CPU8(MACCPU):0-7,16-23#这些内核绑定到numa0NUMAnode1CPU(s):8-15,24-31#这些内核绑定到numa14)PartnershipAlgorithmLinux内核使用著名的伙伴算法作为Allocatea一组连续的页框来建立健壮稳定的内存分配策略。它是内核中的内存分配器,解决了内存管??理外部碎片的问题。外部碎片是指频繁请求和释放不同大小的碎片。一组连续的页框必然会导致许多小的空闲页框块分散在分配的页框块中。5)Slab机制slab机制的核心思想是从对象的角度来管理内存,主要解决内部碎片。内部碎片是由于使用了固定大小的内存分区,即以固定大小的块为单位进行分配。使用这种方法,进程分配的内存可能比需要的大,超出部分就是内部碎片。slab也是内核中的内存分配器。slab分配器是基于对象来管理的。所谓对象就是内核中的数据结构(例如:task_struct、file_struct等)。相同类型的对象归为一类。每当要申请这样一个对象时,slab分配器从一个slab链表中分配一个这个大小的单元,当要释放时,重新保存在链表中。而不是直接返回好友系统,从而避免内部碎片化。上面说的dentry对象是slaballocator分配的对象。slab和partnersystem是上下层的调用关系。partnership按照page管理内存,slab按照byte管理内存。slab首先从伙伴系统中获取若干页的内存,然后将其切割成固定的小块(称为对象)。然后根据声明的对象数据结构分配对象。6)进程内存分配所有进程都必须占用一定的内存,用于存放从磁盘加载的程序代码,或者存放用户输入的数据等。内存可以预先静态分配回收,也可以动态分配回收一经请求。一个普通进程对应的内存空间包含5个不同的数据区:代码段(text):程序代码在内存中的映射,存放函数体的二进制代码,通常用于存放程序执行代码(即CPU执行的代码(MachineDirective))。b.数据段(data):存放程序中已经初始化过且初始值不为0的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读写。C。BSS段(bss):未初始化的全局变量和静态局部变量。d.堆(heap):动态分配的内存段,大小不固定,可以动态扩展(通过malloc等函数分配内存)或动态缩小(通过free等函数释放)。e.堆栈(stack):存放临时创建的局部变量。Linux内核在操作系统中具有最高的优先级。内核函数在申请内存时必须及时分配合适的内存。用户态进程申请内存并不紧急。内核试图尽可能推迟动态分配内存给用户模式进程。A。请求分页,推迟到进程要访问的页面不在RAM中,并引发pagefault异常。b.Copy-on-write(COW),父子进程共享页框而不是复制页框,但是共享的页框不能被修改。只有当父/子进程试图重写共享页框时,内核才会将共享页框复制一个新的页框并标记为可写。7)Linux内存检测工具a、free命令可以监控系统内存$free-htotalusedfreesharedbuff/cacheavailableMem:31Gi13Gi8.0Gi747Mi10Gi16GiSwap:2.0Gi321Mi1.7Gibtop命令查看系统内存和进程内存?VIRTVirtualMemorySize(KiB):进程使用的所有虚拟内存,包括代码(code)、数据(data)、共享库(sharedlibraries),以及换出(swapout)到交换区并映射(map)但尚未使用(未加载到物理内存中)。?RESResidentMemorySize(KiB):进程占用的所有物理内存(physicalmemory),不包括被换出到交换区的部分。?SHRSharedMemorySize(KiB):进程可读的整个共享内存,不是所有部分都包含在RES中。它反映了可能被其他进程共享的内存部分。C。smaps文件cat/proc/$pid/smaps查看某个进程的虚拟内存空间分布0082f000-00852000rw-p0022f00008:054326085/usr/bin/nginx/sbin/nginxSize:140kBRss:140kBPss:78kBShared_Clean:56kBShared_Dirty:68kBPrivate_Clean:4kBPrivate_Dirty:4kBReferenced:120kBAnonymous:80kBAnonHugePages:0kBSwap:0kBKernelPageSize:4kBMMUPageSize:4kBd,vmstatvmstat是VirtualMemoryStatistics系统虚拟内存、进程、CPU活动的缩写。##每秒统计3次$vmstat13procs----------memory-------------------swap-------io------system-------cpu-----rbswpdfreebuffcachesisobiboincsussyidwast0002334838407583042079559600010000100000000233483936758304207955960000105215690000000233483042079559600009661558000000E,Memprocmefile/linuxinfo系统使用的详细信息系统内存使用情况。$cat/proc/meminfoMemTotal:8052444kBMemFree:2754588kBMemAvailable:3934252kBBuffers:137128kBCached:1948128kBSwapCached:0kBActive:3650920kBInactive:1343420kBActive(anon):2913304kBInactive(anon):727808kBActive(file):737616kBInactive(file):615612kBUnevictable:196kBMlocked:196kBSwapTotal:8265724kBSwapFree:8265724kBDirty:104kBWriteback:0kBAnonPages:2909332kBMapped:815524kBShmem:732032kBSlab:153096kBSReclaimable:99684kBSUnreclaim:53412kBKernelStack:14288kBPageTables:62192kBNFS_Unstable:0kBBounce:0kBWritebackTmp:0kBCommitLimit:12291944kBCommitted_AS:11398920kBVmallocTotal:34359738367kBVmallocUsed:0kBVmallocChunk:0kBHardwareCorrupted:0kBAnonHugePages:1380352kBCmaTotal:0kBCmaFree:0kBHugePages_Total:0HugePages_Free:0HugePages_Rsvd:0HugePages_Surp:0Hugepagesize:2048kBDirectMap4k:201472kBDirectMap2M:5967872kBDirectMap1G:3145728kB总结部分中一些内容来源于《深入理解Linux内核》,一些内容根据个人理解写出的,welcometocorrectmeifIamwrong,somepicturescomefromtheInternet
