一、问题

近日我们运维亲戚接到线上LB(负载均衡)服务显存报案linux培训,运维朋友反馈说LB集群有部份机器的显存使用率超过80%,有的甚至超过90%,但是显存使用率还再不停的下降。接到显存报案的消息,让整个团队都比较紧张,我们团队负责的LB服务是零售、物流、科技等业务服务的流量入口linux查看c内存,承接上万个服务的流量转发,一旦有故障影响业务服务比较多,必须马上着手解决显存上涨的问题。目前只是显存报案,暂时不影响业务,先将显存使用率90%以上的LB服务下线,避免显存过低引致LB服务崩溃,影响业务,运维朋友密切关注相关的显存报案的消息。

二、排查过程

经过开发朋友通过cat/proc/meminfo查看Slab的内核显存可能有泄露。

$ cat /proc/meminfo
MemTotal:       65922868 kB
MemFree:         9001452 kB
...
Slab:           39242216 kB
SReclaimable:   38506072 kB
SUnreclaim:       736144 kB
....

通过slabtop剖析slab发觉内核中dentry对象占比高,考虑到dentry对象跟文件有关,Linux中一切皆可以为文件,这个可能跟socket文件有关,通过进一步排查发觉LB服务上有个curl发送的HTTPS侦测,这个存在dentry对象泄露,但是在curl峰会上找到一篇文章确认了这个问题,这个文章说明了curl-7.19.7版本在发送HTTPS恳求时,curl依赖的NSS库存在dentry泄露的bug,我查看一下我们curl版本就是7.19.7,问题总算真相大白了!!!

$ curl -V
curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz
$ rpm -aq|grep nss-
nss-util-3.16.1-3.el6.x86_64
nss-sysinit-3.16.1-14.el6.x86_64
nss-softokn-freebl-3.14.3-17.el6.x86_64
nss-softokn-3.14.3-17.el6.x86_64
nss-3.16.1-14.el6.x86_64
nss-tools-3.16.1-14.el6.x86_64

文章中介绍可以设置环境变量NSS_SDB_USE_CACHE修补这个bug,我们验证通过了这个解决方案。

三、解决方案

1、目前先将侦测脚本停止,在业务流量低峰时将显存使用率超过90%的服务先通过drop_caches清除一下缓存。

2、等大促之后,侦测脚本中设置环境变量NSS_SDB_USE_CACHE,彻底修补这个问题。

四、复盘和总结

内存查看器_内存查看linux_linux查看c内存

此次显存上涨的问题根本缘由是curl-7.19.7依赖的NSS库存在dentry泄露的bug造成的,侦测脚本只是将这个问题曝露下来。此次问题由Linux显存泄露引起的问题,因而以点带面再度系统学习一下Linux显存管理的知识十分有必要,对我们之后排查显存上涨的问题十分有帮助。

1)Linux显存轮询

Linux内核主要通过虚拟显存管理进程的地址空间,内核进程和用户进程都只会分配虚拟显存,不会分配化学显存,通过显存轮询将虚拟显存与化学显存做映射。Linux内核中有三种地址,

a、逻辑地址,每位逻辑地址都由一段(segment)和偏斜量(offset)组成,偏斜量指明了从段开始的地方到实际地址之间的距离。

b、线性地址,又称虚拟地址,是一个32个无符号整数,32位机器显存高达4GB,一般用十六补码数字表示,Linux进程的显存通常说的都是这个显存。

c、物理地址linux串口驱动,用于显存芯片级显存单元轮询。它们与从CPU的地址引脚发送到显存总线上的联通号对应。

Linux中的显存控制单元(MMU)通过一种称为分段单元(segmentationunit)的硬件电路把一个逻辑地址转换成线性地址,接着,第二个称为分页单元(pagingunit)的硬件电路把线性地址转换成一个数学地址。

内存查看linux_linux查看c内存_内存查看器

2)Linux分页机制

分页单元把线性地址转换成化学地址。线性地址被分成以固定宽度为单位的组,称为页(page)。页内部连续的线性地址被映射到连续的数学地址中。通常"页"既指一组线性地址,又指包含这组地址中的数据。分页单元把所有的RAM分成固定宽度的页框(pageframe),也成化学页。每一页框包含一个页(page),也就是说一个页框的宽度与一个页的宽度一致。页框是寻址的一部份,因而也是一个储存区域。分辨一页和一个页框是很重要的,后者只是一个数据块,可以储存任何页框或则c盘中。把线性地址映射到化学地址的数据结构称为页表(pagetable)。页表储存在寻址中,并在启用分页单元之前必须有内核对页表进行适当的初始化。

x86_64的Linux内核采用4级分页模型,通常一页4K,4种页表:

a、页全局目录

b、页上级目录

c、页中间目录

d、页表

页全局目录包含若干页上级目录,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每位页表项指向一个页框。线性地址被分成5部份。

内存查看器_linux查看c内存_内存查看linux

3)NUMA构架

随着CPU步入多核时代,多核CPU通过一条数据总线访问显存延后很大,因而NUMA构架应运而生,NUMA构架全称为非一致性显存构架(NonUniformMemoryArchitecture),系统的化学显存被界定为几个节点(node),每位node绑定不同的CPU核,本地CPU核直接访问本地显存node节点延后最小。

内存查看器_linux查看c内存_内存查看linux

可以通过lscpu查看NUMA与CPU核的关系。

$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                32
On-line CPU(s) list:   0-31
Thread(s) per core:    2
Core(s) per socket:    8
Socket(s):             2
NUMA node(s):          2
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 62
Stepping:              4
CPU MHz:               2001.000
BogoMIPS:              3999.43
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              20480K
NUMA node0 CPU(s):     0-7,16-23      #这些核绑定在numa 0
NUMA node1 CPU(s):     8-15,24-31     #这些核绑定在numa 1

4)伙伴关系算法

Linux内核通过知名伙伴关系算法为分配一组连续的页框而构建一种强壮、稳定的显存分配策略,是内核中一种显存分配器,并解决了显存管理外碎片的问题,外碎片是指频繁地恳求和释放不同大小的一组连续页框,必然引起在已分配的页框的块分散了许多小块的空闲页框。

5)Slab机制

slab机制的核心思想是以对象的观点来管理显存,主要是为了解决内部碎片,内部碎片是因为采用固定大小的显存分区,即以固定的大小块为单位来分配,采用这些技巧,进程所分配的显存可能会比所须要的大,这多余的部份便是内部碎片。slab也是内核中一种显存分配器,slab分配器基于对象进行管理的,所谓的对象就是内核中的数据结构(比如:task_struct,file_struct等)。相同类型的对象归为一类,每每要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统linux查看c内存,进而防止内部碎片。里面中说到的dentry对象就是通过slab分配器分配的一种对象。

slab和伙伴系统是上下级的调用关系,伙伴关系依照页管理显存,slab根据字节管理,slab先从伙伴系统获取数个页的显存,之后劈成分成固定的小块(称为object),之后再根据申明的对象数据结构分配对象。

6)进程显存分布

所有进程都必须占用一定数目的显存,这种显存拿来储存从c盘载入的程序代码,或储存来自用户输入的数据等。显存可以提早静态分配和统一回收,也可以按需动态分配和回收。对于普通进程对应的显存空间包含5种不同的数据区:

a、代码段(text):程序代码在显存中的映射,储存函数体的二补码代码,一般用于储存程序执行代码(即CPU执行的机器指令)。

b、数据段(data):储存程序中已初始化且终值不为0的全局变量和静态局部变量。数据段属于静态显存分配(静态储存区),可读可写。

c、BSS段(bss):未初始化的全局变量和静态局部变量。

d、堆(heap):动态分配的显存段,大小不固定,可动态扩张(malloc等函数分配显存),或动态削减(free等函数释放)。

e、栈(stack):储存临时创建的局部变量。

内存查看linux_内存查看器_linux查看c内存

Linux内核是操作系统中优先级最高的,内核函数申请显存必须及时分配适当的显存,用户态进程申请显存被觉得是不急迫的,内核尽量延后给用户态的进程动态分配显存。

a、请求调页,延后到进程要访问的页不在RAM中时为止,引起一个缺页异常。

内存查看linux_内存查看器_linux查看c内存

b、写时复制(COW),父、子进程共享页框而不是复制页框,并且共享页框不能被更改,只有当父/子进程企图改写共享页框时,内核才将共享页框复制一个新的页框并标记为可写。

7)Linux显存检查工具

a、free可以监控系统显存

$ free -h
              total        used        free      shared  buff/cache   available
Mem:           31Gi        13Gi       8.0Gi       747Mi        10Gi        16Gi
Swap:         2.0Gi       321Mi       1.7Gi

b、top命令查看系统显存以及进程显存

•VIRTVirtualMemorySize(KiB):进程使用的所有虚拟显存,包括代码(code)、数据(data)、共享库(sharedlibraries),以及被换出(swapout)到交换区和映射了(map)但仍未使用(未载入实体显存)的部份。

•RESResidentMemorySize(KiB):进程所占用的所有实体显存(physicalmemory),不包括被换出到交换区的部份。

•SHRSharedMemorySize(KiB):进程可读的全部共享显存,并非所有部份都包含在RES中。它反映了可能被其他进程共享的显存部份。

c、smaps文件

cat/proc/$pid/smaps查看某进程虚拟显存空间的分布情况

0082f000-00852000 rw-p 0022f000 08:05 4326085    /usr/bin/nginx/sbin/nginx
Size:                140 kB
Rss:                 140 kB
Pss:                  78 kB
Shared_Clean:         56 kB
Shared_Dirty:         68 kB
Private_Clean:         4 kB
Private_Dirty:        12 kB
Referenced:          120 kB
Anonymous:            80 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB

d、vmstat

vmstat是VirtualMeomoryStatistics(虚拟显存统计)的简写,可实时动态监视操作系统的虚拟显存、进程、CPU活动。

## 每秒统计3次
$ vmstat 1 3

linux查看c内存_内存查看器_内存查看linux

procs -----------memory---------------- ---swap-- -----io---- --system-- -----cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 233483840 758304 20795596 0 0 0 1 0 0 0 0 100 0 0 0 0 0 233483936 758304 20795596 0 0 0 0 1052 1569 0 0 100 0 0 0 0 0 233483920 758304 20795596 0 0 0 0 966 1558 0 0 100 0 0

e、meminfo文件

Linux系统中/proc/meminfo这个文件拿来记录了系统显存使用的详尽情况。

$ cat /proc/meminfo
MemTotal:        8052444 kB
MemFree:         2754588 kB
MemAvailable:    3934252 kB
Buffers:          137128 kB
Cached:          1948128 kB
SwapCached:            0 kB
Active:          3650920 kB
Inactive:        1343420 kB
Active(anon):    2913304 kB
Inactive(anon):   727808 kB
Active(file):     737616 kB
Inactive(file):   615612 kB
Unevictable:         196 kB
Mlocked:             196 kB
SwapTotal:       8265724 kB
SwapFree:        8265724 kB
Dirty:               104 kB
Writeback:             0 kB
AnonPages:       2909332 kB
Mapped:           815524 kB
Shmem:            732032 kB
Slab:             153096 kB
SReclaimable:      99684 kB
SUnreclaim:        53412 kB
KernelStack:       14288 kB
PageTables:        62192 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    12291944 kB
Committed_AS:   11398920 kB
VmallocTotal:   34359738367 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
HardwareCorrupted:     0 kB
AnonHugePages:   1380352 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      201472 kB
DirectMap2M:     5967872 kB
DirectMap1G:     3145728 kB

总结部份中一些内容来始于《深入理解Linux内核》,一些内容依照个人理解写出的,有不对地方欢迎见谅,部份图片来始于网路

本文原创地址://gulass.cn/xslfwncbjpcy.html编辑:刘遄,审核员:暂无