管理内存是操作系统的核心职责之一。Linux系统把内存分页管理,每一页(page)的大小可以用命令getconf PAGESIZE查看,单位是字节(Byte),Linux默认的页大小为4KB。

[root@localhost ~]# getconf PAGESIZE

4096

内存的总体情况可以通过cat /proc/meminfo查看

[root@localhost ~]# cat /proc/meminfo

MemTotal:       65751096 kB

MemFree:        64077144 kB

MemAvailable:   64061216 kB

Buffers:              2076 kB

Cached:           404000 kB

SwapCached:              0 kB

Active:           759688 kB

Inactive:         177952 kB

Active(anon):     532748 kB

Inactive(anon):    17212 kB

Active(file):     226940 kB

Inactive(file):   160740 kB

Unevictable:           0 kB

Mlocked:                 0 kB

SwapTotal:      33554428 kB

SwapFree:       33554428 kB

Dirty:                 8 kB

Writeback:               0 kB

AnonPages:        531796 kB

Mapped:            54212 kB

Shmem:             18396 kB

Slab:             194488 kB

SReclaimable:      72256 kB

SUnreclaim:       122232 kB

KernelStack:       13360 kB

PageTables:        12020 kB

NFS_Unstable:          0 kB

Bounce:                0 kB

WritebackTmp:          0 kB

CommitLimit:    66429976 kB

Committed_AS:    2371680 kB

VmallocTotal:   34359738367 kB

VmallocUsed:      415224 kB

VmallocChunk:   34308620284 kB

HardwareCorrupted:     0 kB

AnonHugePages:    290816 kB

HugePages_Total:       0

HugePages_Free:        0

HugePages_Rsvd:        0

HugePages_Surp:        0

Hugepagesize:       2048 kB

DirectMap4k:      217920 kB

DirectMap2M:     8124416 kB

DirectMap1G:    60817408 kB

本文主要总结一下在运维实践中遇到的跟内存有关的问题或技巧。

buffer cache和swap

free是系统管理员查看服务器内存情况常用的命令,在CentOS7.4上输出内容如下:

[root@localhost ~]# free

              total        used        free      shared    buff/cache   available

Mem:         65751096     1079756    64070284         18396      601056    64054484

Swap:        33554428           0    33554428

从CentOS7系列版本开始,buffer和cache被合并为一列显示了,在之前的版本中是分开的。虽然被合并显示,但是它们保存的内容是不同的。

l  Buffer——顾名思义是缓冲区的意思,由于内存访问速度远超硬盘等块设备,所以Linux系统把文件系统中访问过的元数据缓存在buffer中,以提高下次访问的速度。

l  Cache——cache是缓存区,在内存足够的情况下,Linux系统总是倾向于把打开过的程序、文件内容缓存在内存中,所以长时间运行的服务器cache都比较大,这么做是为了下次访问这些文件更快。当有程序申请内存且空闲内存不足时,系统会释放cache以腾出空间。

l  Swap——在以前内存还是很金贵的资源时,为了“扩展”服务器有限的内存,Linux允许在块设备上划分出一块空间,将内存中不经常被访问的页置换到这里,腾出内存给处于活跃状态的进程,这个空间就是交换分区swap。由于硬盘比内存访问性能低,所以使用swap分区会降低性能。现在的服务器有一两百GB内存已经不稀奇了,似乎swap已经没有存在的意义了,其实不然,即使swap现在已经很少被用到了,我们在部署服务器时仍应该分配一个合理的空间给swap,有swap分区作为缓冲,可以避免系统因内存不足开始自动kill进程引起宕机,给运维人员处理内存饱和的问题争取时间,性能损失总比宕机要好。

实际情况中,有时会遇到有的服务器内存被cache占用完,系统开始把部分内存置换到swap中,导致性能急剧下降。这种情况下可以清理cache,释放出内存给进程用。清理cache的过程如下:

[root@localhost ~]# sync

[root@localhost ~]# echo 3 > /proc/sys/vm/drop_caches

为了安全,清理cache前要先执行sync,使内存中的dirty数据落盘。

drop_caches取值范围为1,2,3,

1表示清理页面缓存;

2表示清理页面缓存和目录缓存;

3表示清理页面缓存、目录缓存和inode缓存。

为了减小系统使用swap分区的倾向,还有个内核参数可以调整——swappiness。swappiness的取值范围是0到100,数值越大系统使用swap分区的倾向就越大,为0时表示完全不使用swap。鉴于上面介绍过swap分区存在的必要性,我们不应该把swappiness设置为0,而应该设置为1,让系统尽可能避免使用swap分区,同时并不完全放弃使用swap的可能。

[root@localhost ~]# echo "vm.swappiness = 1" >> /proc/sys/vm/swappiness

[root@localhost ~]# sysctl -p

vm.swappiness   = 1

KSM

KSM(Kernel SamePage Merging)是一种压缩内存的技术,顾名思义就是将内存中内容相同的页进行合并以节省内存空间,当有进程需要修改该内存页的内容时,再将其拷贝一份再修改,也就是“写时复制copy on write”。KSM最早是为了优化qemu-kvm宿主机内存使用而诞生的,当一台宿主机上运行多个相同操作系统的虚拟机时,有很多内存页内容相同可以合并。KSM在CentOS6和CentOS7上是默认打开的,有两个相关服务:

l  ksm

l  Ksmtuned

ksmtuned依赖ksm服务,它的主体/usr/sbin/ksmtuned是一个shell脚本,通过读取配置文件/etc/ksmtuned.conf中的参数,对ksm的行为进行控制。在ksmtuned的控制下,ksm并不是一直在工作,它只在参数限定的条件达到的情况下工作,ksm是否在工作可以通过cat /sys/kernel/mm/ksm/run命令查看,0表示ksm没有在工作,1表示ksm在工作。

通过分析/usr/sbin/ksmtuned,可以了解配置文件中几个主要参数的含义:

l  KSM_MONITOR_INTERVAL=60 默认值为60,单位秒,表示每隔60秒,ksmtuned工作一次,扫描系统内存使用情况并据此调整ksm参数。数值越小ksmtuned工作频率越高,对cpu消耗越多;

l  KSM_SLEEP_MSEC=10 默认值为10,最小值也为10,单位毫秒,ksm扫描内存页的间隔,数值越小ksm扫描内存页的频率越高,对cpu消耗越多。对应的内核参数是/sys/kernel/mm/ksm/sleep_millisecs;

l  KSM_NPAGES_BOOST=300

KSM_NPAGES_DECAY=-50

KSM_NPAGES_MIN=64

KSM_NPAGES_MAX=1250

这一组参数用于设置ksm一次扫描多少内存页,对应内核参数/sys/kernel/mm/ksm/pages_to_scan。pages_to_scan的值在KSM_NPAGES_MIN和KSM_NPAGES_MAX之间波动,如果空闲内存大于临界值,需要降低ksm工作强度,则每次ksmtuned工作都会调低pages_to_scan 300(KSM_NPAGES_BOOST)页;反之,需要增强ksm工作强度,则每次ksmtuned工作会调高pages_to_scan 50(KSM_NPAGES_DECAY)页。

l  KSM_THRES_COEF=20

KSM_THRES_CONST=2048

       这两个参数用于设置一个内存临界值,总内存的20%(KSM_THRES_COEF)和2048MB(KSM_THRES_CONST)两者之间取大者。当“所有qemu进程的内存”和临界值之和小于“总内存”,并且空闲内存大于临界值时,ksm停止工作,/sys/kernel/mm/ksm/run被置0;其他情况下,ksm处于工作状态,/sys/kernel/mm/ksm/run被置1。如果想让ksm早点开始工作,可以调高临界值。

       常见的ksmtuned.conf配置如下:

# Configuration file for ksmtuned.

 

# How long ksmtuned should sleep between tuning   adjustments

KSM_MONITOR_INTERVAL=60

 

# Millisecond sleep between ksm scans for 16Gb server.

# Smaller servers sleep more, bigger sleep less.

KSM_SLEEP_MSEC=10

 

KSM_NPAGES_BOOST=300

KSM_NPAGES_DECAY=-50

KSM_NPAGES_MIN=64

KSM_NPAGES_MAX=1250

 

KSM_THRES_COEF=20

KSM_THRES_CONST=2048

 

# uncomment the following if you want ksmtuned debug   info

 

LOGFILE=/var/log/ksmtuned

DEBUG=1

       日志/var/log/ksmtuned记录了ksmtuned每次执行的结果:

[root@localhost ~]# tail -100f   /var/log/ksmtuned

Thu Jan 18 11:29:18 CST 2018: total   65751096

Thu Jan 18 11:29:18 CST 2018: sleep 10

Thu Jan 18 11:29:18 CST 2018: thres   13150219

Thu Jan 18 11:30:18 CST 2018: committed 0   free 64470428

Thu Jan 18 11:30:18 CST 2018: 13150219   < 65751096 and free > 13150219, stop ksm

我的测试机内存比较空闲,ksm一直处于stop状态,下面我把KSM_THRES_COEF调大到100然后systemctl restart ksmtuned,强行让ksm工作,然后看看效果:

[root@localhost ~]# cat /sys/kernel/mm/ksm/run

1

[root@localhost ~]# tail -10f /var/log/ksmtuned

Thu Jan 18 15:05:05 CST 2018: sleep 10

Thu Jan 18 15:05:05 CST 2018: thres 65751096

Thu Jan 18 15:06:05 CST 2018: committed 0 free 64467712

Thu Jan 18 15:06:05 CST 2018: 65751096 > 65751096, start ksm

Thu Jan 18 15:06:05 CST 2018: 64467712 < 65751096, boost

Thu Jan 18 15:06:05 CST 2018: KSMCTL start 300 10

Thu Jan 18 15:07:05 CST 2018: committed 0 free 64468624

Thu Jan 18 15:07:05 CST 2018: 65751096 > 65751096, start ksm

Thu Jan 18 15:07:05 CST 2018: 64468624 < 65751096, boost

Thu   Jan 18 15:07:05 CST 2018: KSMCTL start 600 10

/sys/kernel/mm/ksm/pages_sharing记录了有多少内存页正在使用被合并的内存页,也就是实际节省的内存页数。页数乘以PAGESIZE就可以计算出实际节省的内存,命令如下:

echo$(($(cat   /sys/kernel/mm/ksm/pages_sharing)*$(getconf PAGESIZE)/1024/1024))MB”

 

NUMA

给有两路cpu的服务器插过内存的同学应该知道,每路cpu都有自己的内存条插槽。这是解决多cpu扩展问题的一种架构——NUMA,Non-Uniform Memory Access,非一致内存访问。为什么叫“非一致”呢?因为一个cpu访问自己的本地内存比访问别的cpu的内存速度要快,所以叫非一致。

numactl是一个管理numa的命令行工具,如果系统默认没有这个命令,可以通过yum install numactl安装。numactl –hardware可以查看cpu和内存的硬件情况,下面是一台4路服务器上执行命令的结果:

[root@localhost ~]# numactl --hardware

available: 4 nodes (0-3)

node 0 cpus: 0 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60

node 0 size: 16338 MB

node 0 free: 15279 MB

node 1 cpus: 1 5 9 13 17 21 25 29 33 37 41 45 49 53 57 61

node 1 size: 16384 MB

node 1 free: 15762 MB

node 2 cpus: 2 6 10 14 18 22 26 30 34 38 42 46 50 54 58 62

node 2 size: 16384 MB

node 2 free: 15646 MB

node 3 cpus: 3 7 11 15 19 23 27 31 35 39 43 47 51 55 59 63

node 3 size: 16384 MB

node 3 free: 15861 MB

node distances:

node   0   1   2   3

  0:  10  20  30  20

  1:  20  10  20  30

  2:  30  20  10  20

  3:  20  30  20  10

4路cpu就是4个numa nodes,每个nodes有16G的本地内存,整个服务器总共有64G物理内存。

numastat -c 可以查看指定进程的内存使用情况:

[root@localhost ~]# numastat -c nginx

 

Per-node process memory usage (in MBs)

PID              Node 0 Node 1 Node 2 Node 3 Total

-------------  ------ ------ ------ ------ -----

74675 (nginx)       0      0      1      0     1

74676 (nginx)       0      0      2      0     3

74677 (nginx)       1      0      1      0     3

74678 (nginx)       1      1      1      0     3

74679 (nginx)       0      0      2      0     3

74680 (nginx)       1      0      1      0     3

74681 (nginx)       0      1      1      0     3

74682 (nginx)       0      0      2      0     3

74683 (nginx)       1      0      1      0     3

74684 (nginx)       0      0      2      0     3

74685 (nginx)       0      1      1      0     3

74686 (nginx)       1      0      1      0     3

74687 (nginx)       0      0      2      0     3

74688 (nginx)       0      0      2      0     3

74689 (nginx)       0      1      1      0     3

74690 (nginx)       1      0      1      0     3

74691 (nginx)       0      0      2      0     3

74692 (nginx)       0      0      2      0     3

74693 (nginx)       0      1      1      0     3

74694 (nginx)       2      0      1      1     3

74695 (nginx)       0      0      2      0     3

74696 (nginx)       0      0      2      0     3

74697 (nginx)       1      1      1      0     3

74698 (nginx)       1      0      1      0     3

74699 (nginx)       0      0      2      0     3

-------------  ------ ------ ------ ------ -----

Total              18      6     35      5    65

上面的输出可以看出各个nginx进程的内存分散在了4个node上。

CentOS6和Centos7上都有一个叫numad的服务,这个服务负责在numa架构下调度各节点的内存分配,使内存分配具有cpu亲和性,例如一个线程运行在cpu0上,它申请的内存页将尽量从cpu0的本地内存中分配。这个服务默认没有开启,因为这种内存分配策略并不适合所有场景,当服务器上运行了很多应用程序或很多虚拟机时,numad具有cpu亲和性的内存分配方式可以提高性能,但是如果整个系统只运行一个高内存消耗的程序如数据库,numad反而会导致内存分配不均而降低性能,因此数据库服务器建议不要开启numad服务。

Linux系统中还有一个numa自动平衡策略,系统通过自动调配内存以求在各个numa nodes的内存使用率处于相对平衡的状态,这个策略可以通过修改内核参数/proc/sys/kernel/numa_balancing控制。关闭numa自动平衡策略:echo 0 > /proc/sys/kernel/numa_balancing;开启numa自动平衡策略:echo 1 > /proc/sys/kernel/numa_balancing。这个策略系统默认也没开启。

 

关于Linux内存的小技巧暂时只想到这么多,感谢阅读!