写点什么

Linux 性能调优之内存负载调优的一些笔记

作者:山河已无恙
  • 2022 年 8 月 01 日
  • 本文字数:16425 字

    阅读完需:约 54 分钟

写在前面

  • 整理一些 Linux 内存调优的笔记,分享给小伙伴

  • 博文没有涉及的 Demo,理论方法偏多,可以用作内存调优入门

  • 博文内容涉及:

  • Linux 内存管理的基本理论

  • 寻找内存泄露的进程

  • 内存交换空间调优

  • 不同方式的内存回收

  • 食用方式 :

  • 需了解 Linux 基础知识

  • 理解不足小伙伴帮忙指正

原谅和忘记就意味着扔掉了我们获得的最贵经验 -------《人生的智慧》叔本华



在现代处理器中,与 CPU 执行代码或处理信息相比,向内存子系统保存信息或从中读取信息一般花费的时间更长


通常,在CPU执行指令或处理数据前,它会消耗相当多的空闲时间来等待从内存中取出指令和数据。处理器用不同层次的高速缓存(cache)来弥补这种缓慢的内存性能。

内存管理

内存是内核所做的比较复杂的事情之一。高效的内存管理对于系统中进程的良好性能至关重要。现代计算机系统使用分页来安全、灵活地管理系统内存。


为了提高效率,Linux 将其分成内存“页”。当对内存进分配或传送时,Linux操作的单位是页,而不是单个字节。在报告一些内存统计数据时,Linux 内核报告的是每秒页面的数量,该值根据其运行的架构可以发生变化。


系统上的物理 RAM 被分为页帧;一页帧保存一页数据。进程不直接寻址物理内存。相反,每个进程都有一个虚拟地址空间。当进程被分配内存时,页帧的物理地址被映射到进程的一个虚拟地址。从进程的角度来看,它有一个私有的内存空间,它只能看到映射到它的一个虚拟地址的物理页帧。


进程虚拟地址空间(页面)的大小取决于处理器架构。在 32 位 i386 系统上,一个进程的虚拟地址空间可以容纳 2^32 个字节(4gib)的内存;在 64 位 x86-64 系统上,地址空间的大小是 2^64 字节(16 EiB)。然而,单个进程通常不会使用它的整个地址空间;它的大部分是未分配的,也没有映射到任何实际的物理内存。但是虚拟地址空间的大小确实限制了进程可以拥有的最大内存


一些内存涉及的名词解释

交换分区(物理内存不足)

所有系统RAM芯片的物理内存容量都是固定的。即使应用程序需要的内存容量大于可用的物理内存,Linux 内核仍然允许这些程序运行。Linux 内核使用硬盘作为临时存储器,这个硬盘空间被称为交换分区(swap space)


尽管交换是让进程运行的极好的方法,但它却慢的要命。与使用物理内存相比,应用程序使用交换的速度可以慢到一千倍。如果系统性能不佳,确定系统使用了多少交换通常是有用的。

缓冲区(buffer)和缓存(cache)(物理内存太多)

缓存(cache)

相反,如果你的系统物理内存容量超过了应用程序的需求,Linux 就会在物理内存中缓存近期使用过的文件,这样,后续访问这些文件时就不用去访问硬盘了。


对要频繁访问硬盘的应用程序来说,这可以显著加速其速度,显然,对经常启动的应用程序而言,这是特别有用的。


应用程序首次启动时,它需要从硬盘读取;但是,如果应用程序留着缓存中,那它就需要从更快速的物理内存读取。

缓冲区(buffer)

Linux 还使用了额外的存储作为缓冲区。为了进一步优化应用程序,Linux为需要被写回硬盘的数据预留了存储空间。这些预留空间被称为缓冲区。如果应用程序要将数据写回硬盘,通常需要花费较长时间,Linux 让应用程序立刻继续执行,但将文件数据保存到内存缓冲区。在之后的某个时刻,缓冲区被刷新到硬盘,而应用程序可以立即继续


高速缓存和缓冲区的使用使得系统内空闲的内存很少,默认情况下,Linux 试图尽可能多的使用你的内存。这是好事。


如果Linux侦测到有空闲内存,它就会将应用程序和数据缓存到这些内存以加速未来的访问。由于访问内存的速度比访问硬盘的速度快了几个数量级,因此,这就可以显著地提升整体性能


如果系统需要缓存空间做更重要的事情,那么缓存空间将被擦除并交给系统。之后,对原来被缓存对象的访问就需要转向硬盘来满足。

活跃与非活跃内存

  • 活跃内存是指当前被进程使用的内存。

  • 不活跃内存是指已经被分配了,但暂时还未使用的内存。


这两种类型的内存没有本质上的区别。需要时,Linux 找出进程最近最少使用的内存页面,并将它们从活跃列表移动到不活跃列表。当要选择把哪个内存页交换到硬盘时,内核就从不活跃内存列表中进行选择

内核的内存使用情况(分片)

除了应用程序需要分配内存外,Linux内核也会为了记账的目的消耗一定量的内存。


记账包括,比如跟踪从网络或磁盘I/O来的数据,以及跟踪哪些进程正在运行,哪些正在休眠。为了管理记账,内核有一系列缓存,包含一个或多个内存分片。每个分片为一组对象,个数可以是一个或多个。

内核消耗的内存分片数量取决于使用的是Linux内核的哪些部分,而且还可以随着机器负载类型的变化而变化。

进程内存

当进程试图访问虚拟内存中尚未分配物理页面的页面时, 即应用程序中没有加载的内存中数据,将触发页面错误

  • 如果需要分配一个新的物理内存页,这被称为次要页面故障

  • 如果内核需要从磁盘检索一页内存,则称为重大页面故障


发生重大页面错误的原因包括恢复已换出的虚拟内存页面,或将数据或代码从可执行文件映射到内存。次要页面错误会导致少量开销,但是重大页面错误会对性能产生重大影响。


要查看每个进程的次要和主要页面错误,你可以使用ps命令,询问minfltmajflt列:

┌──[root@liruilongs.github.io]-[~]└─$ ps -o pid,cmd,comm,minflt,majflt $$   PID CMD                         COMMAND         MINFLT MAJFLT 12280 -bash                       bash             11203      0┌──[root@liruilongs.github.io]-[~]└─$ ps -o pid,cmd,comm,minflt,majflt 889   PID CMD                         COMMAND         MINFLT MAJFLT   889 /usr/bin/etcd --name=defaul etcd              7091     90
复制代码

使用 cgroups 限制内存

进程可用的内存量可以用cgroup内存控制器来限制。在内存 cgroup 中有几个相关的文件涉及内核参数:

┌──[root@liruilongs.github.io]-[~]└─$ ls /sys/fs/cgroup/memory/cgroup.clone_children           memory.kmem.tcp.limit_in_bytes      memory.oom_controlcgroup.event_control            memory.kmem.tcp.max_usage_in_bytes  memory.pressure_levelcgroup.procs                    memory.kmem.tcp.usage_in_bytes      memory.soft_limit_in_bytescgroup.sane_behavior            memory.kmem.usage_in_bytes          memory.statdocker                          memory.limit_in_bytes               memory.swappinessmemory.failcnt                  memory.max_usage_in_bytes           memory.usage_in_bytesmemory.force_empty              memory.memsw.failcnt                memory.use_hierarchymemory.kmem.failcnt             memory.memsw.limit_in_bytes         notify_on_releasememory.kmem.limit_in_bytes      memory.memsw.max_usage_in_bytes     release_agentmemory.kmem.max_usage_in_bytes  memory.memsw.usage_in_bytes         system.slicememory.kmem.slabinfo            memory.move_charge_at_immigrate     tasksmemory.kmem.tcp.failcnt         memory.numa_stat                    user.slice
复制代码

看一个 docker 进程的相关信息


  • memory.stat: :这个 cgroup 中内存和交换分区的使用情况以及内存总量的详细统计。


dockers 配置了开机自动启动,从 Cgroup 角度看,所以他在是 systemd 这个 slicp 下面,作为一个 service 单元受 CGroup 管理

┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]└─$ cat ./memory.statcache 3657728rss 144375808rss_huge 113246208mapped_file 0swap 0pgpgin 25693pgpgout 23278pgfault 84168pgmajfault 0inactive_anon 4096active_anon 144375808inactive_file 397312active_file 3256320unevictable 0hierarchical_memory_limit 9223372036854771712hierarchical_memsw_limit 9223372036854771712total_cache 3657728total_rss 144375808total_rss_huge 113246208total_mapped_file 0total_swap 0total_pgpgin 25693total_pgpgout 23278total_pgfault 84168total_pgmajfault 0total_inactive_anon 4096total_active_anon 144375808total_inactive_file 397312total_active_file 3256320total_unevictable 0
复制代码


  • memory.limit_in_bytes:在这里写入一个值,以限制这个cgroup中可以使用的用户内存(包括缓存)的数量。字节以外的单位可以通过在被设置的值后面加上 k、m 或 g 来使用。


所以我们可以通过修改这个值来限制 docker 的内存使用

┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]└─$ cat ./memory.limit_in_bytes9223372036854771712
复制代码


$ echo 1G > ./memory.limit_in_bytes
复制代码
  • memory.memsw.limit_in_bytes:类似于memory.limit_in_bytes,但这一次是内存+交换组合。

┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]└─$ cat ./memory.memsw.limit_in_bytes9223372036854771712
复制代码

当然也可以在单位配置文件[Service]部分中添加下面的命令:

MemoryLimit=value
复制代码


cgroup中执行的进程设定其可用内存的最大值,同样,MemoryAccounting 参数必须在同一单元中启用MemoryAccounting=yes打开此单元的进程和内核内存占。接受一个布尔参数 ,这里 MemoryLimit 参数可以控制 memory.limit_in_bytes Cgroup参数实现内存限制


其他的内核参数

  • memory.failcntmemory.memsw.failcnt:这些文件报告内存的频率。

┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]└─$ cat ./memory.failcnt0┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]└─$ cat memory.memsw.failcnt0
复制代码

要查看进程虚拟地址空间是如何使用的,可以使用pmap PID命令,或者查看/proc/PID/maps/proc/PID/maps

┌──[root@liruilongs.github.io]-[~]└─$ pmap 889 | head  -10889:   /usr/bin/etcd --name=default --data-dir=/var/lib/etcd/default.etcd --listen-client-urls=http://192.168.26.55:2379,http://localhost:2379000000c000000000      8K rw---   [ anon ]000000c41ffc6000   6376K rw---   [ anon ]000000c420600000   1216K rw---   [ anon ]00005618c7aaa000  15784K r-x-- etcd00005618c8c14000   8672K r---- etcd00005618c948c000    332K rw--- etcd00005618c94df000    168K rw---   [ anon ]00005618ca601000    132K rw---   [ anon ]00007ff23c000000    132K rw---   [ anon ]
复制代码


┌──[root@liruilongs.github.io]-[~]└─$ cat /proc/889/maps | head  -10c000000000-c000002000 rw-p 00000000 00:00 0c41ffc6000-c420600000 rw-p 00000000 00:00 0                              [stack:889]c420600000-c420730000 rw-p 00000000 00:00 05618c7aaa000-5618c8a14000 r-xp 00000000 08:01 272289927                  /usr/bin/etcd5618c8c14000-5618c948c000 r--p 00f6a000 08:01 272289927                  /usr/bin/etcd5618c948c000-5618c94df000 rw-p 017e2000 08:01 272289927                  /usr/bin/etcd5618c94df000-5618c9509000 rw-p 00000000 00:00 05618ca601000-5618ca622000 rw-p 00000000 00:00 0                          [heap]7ff23c000000-7ff23c021000 rw-p 00000000 00:00 07ff23c021000-7ff240000000 ---p 00000000 00:00 0
复制代码


┌──[root@liruilongs.github.io]-[~]└─$ cat /proc/889/smaps | head  -10c000000000-c000002000 rw-p 00000000 00:00 0Size:                  8 kBRss:                   8 kBPss:                   8 kBShared_Clean:          0 kBShared_Dirty:          0 kBPrivate_Clean:         0 kBPrivate_Dirty:         8 kBReferenced:            8 kBAnonymous:             8 kB┌──[root@liruilongs.github.io]-[~]└─$
复制代码

找到内存泄漏

有时候进程在使用完内存后不能正确地释放内存


如果进程是一个短生命周期的进程,如lsnetstat,这不是一个大问题,因为当一个进程退出时,它的所有内存都会被内核释放,如果它是一个长时间运行的进程,问题可能会变得相当严重。


除了杀死并重新启动进程外,系统管理员在修复内存泄漏方面所能做的事情并不多。识别内存泄漏是系统管理员的职责之一。


要识别内存泄漏,可以使用通用工具,如ps,top,free,sar -rsar -R


但也有专门的工具,如valgrind工具memcheck。要在 memcheck 工具下运行进程,可以使用如下命令:valgrind --tool=memcheck program args-pro


┌──[root@liruilongs.github.io]-[~]└─$ yum -y install valgrind
复制代码


┌──[root@liruilongs.github.io]-[~]└─$ rpm -ivh bigmem-7.0-1.r29766.x86_64.rpm准备中...                          ################################# [100%]正在升级/安装...   1:bigmem-7.0-1.r29766              ################################# [100%]
复制代码

有两种不同类型的内存泄漏需要注意。

  • 在第一种情况下,内存泄露,程序通过 malloc(一种分配内存块的函数)等系统调用请求内存,但实际上并不使用这些内存。这将导致程序的虚拟大小增加(VIRT),以及/proc/meminfo中的Committed_AS(当前在系统上分配的内存量。提交的内存是进程分配的所有内存的总和,即使它还没有被它们“使用”)行,但没有使用实际的物理内存。常驻大小(顶部的RSS)保持(几乎)不变。


对应的参数我么可以通过 top 命令获取,top 版本不同,对应的列名有些差别

top - 11:48:07 up 14 min,  1 user,  load average: 0.07, 0.10, 0.13Tasks: 273 total,   2 running, 271 sleeping,   0 stopped,   0 zombie%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 stKiB Mem : 32931532 total, 23319380 free,  8643596 used,   968556 buff/cacheKiB Swap: 10485756 total, 10485756 free,        0 used. 23759444 avail Mem
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND   889 etcd      20   0 10.559g  29332  11008 S   1.3  0.1   0:10.94 etcd  8089 nginx     20   0   41636  12148   1588 S   1.3  0.0   0:03.35 redis-server
复制代码


  • 在第二种情况下,内存溢出,程序实际使用它分配的内存。这将导致驻留大小与虚拟大小同步增加,从而导致实际的内存短缺。虽然泄漏虚拟内存不是一件好事,但泄漏驻留内存将对系统造成更大的影响。


看一个书里的 Demo,在运行下面的应用程序时,请跟踪内存统计数据。

┌──[root@liruilongs.github.io]-[~]└─$ watch 'free -k;grep Committed_AS /proc/meminfo'Every 1.0s: free -k;grep Committed_AS /proc/meminfo                                             Fri Jul 29 20:47:24 2022
              total        used        free      shared  buff/cache   availableMem:       32927488     4922536    27100148       22264      904804    27579940Swap:      10485756           0    10485756Committed_AS:    6853280 kB

复制代码

首先安装 valgrind,然后在 valgrind 下运行 bigmem 命令,要求分配 256mib 的常驻内存。现在 bigmem 请求 256 MiB 的虚拟内存。

┌──[root@liruilongs.github.io]-[~]└─$ valgrind --tool=memcheck bigmem 256==65614== Memcheck, a memory error detector==65614== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.==65614== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info==65614== Command: bigmem 256==65614==Attempting to allocate 256 Mebibytes of resident memory...Press <Enter> to exit

复制代码

现在我们确定 bigmem 正在泄漏内存,泄露的原因是bigmem 256 导致的

==65614== Command: bigmem 256==65614==Attempting to allocate 256 Mebibytes of resident memory...
复制代码

观察内存变化,Committed_AS ,即分配的虚拟内存增加了

Every 1.0s: free -k;grep Committed_AS /proc/meminfo                                             Fri Jul 29 21:00:49 2022
              total        used        free      shared  buff/cache   availableMem:       32927488     5157320    26858220       22296      911948    27345124Swap:      10485756           0    10485756Committed_AS:    7281208 kB
复制代码

交换空间调优

嗯,这里为了方面,我们把交换分区,交换文件统一称交换分区,或者交换空间


交换分区会增加系统上的有效内存量。当可用内存减少时,可以将不使用的页面换出到磁盘,以释放空间供其他用途。当再次需要这些页时,会发生一个严重的页错误,在使用它们之前,需要将它们再次从磁盘页到内存中。


这为我们提供了另一种方法来释放正在运行的系统上的内存,并有效地使用我们拥有的内存。swap的缺点是,与RAM相比,大多数存储设备都非常慢。在内存中进行换出和换出会显著降低系统的速度。


vmstat 实用程序可以为您提供关于系统是否正在进行分页交换(“交换”)的信息。vmstat输出中的关键列是si/so等等。每秒换入的页面和每秒换出的页面。

┌──[root@liruilongs.github.io]-[~]└─$ vmstat 1 5procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st 1  0      0 27056464   3104 912452    0    0    28    20  259  310  4  2 94  0  0 0  0      0 27055988   3104 912456    0    0     0     0 1583 2023  6  2 93  0  0 1  0      0 27044272   3104 912480    0    0    24    36  845 1214  1  1 98  0  0 1  0      0 27055892   3104 912480    0    0     0     0 1880 2929  5  2 94  0  0 4  0      0 27056416   3104 912484    0    0     0     0 1730 2107  6  2 92  0  0┌──[root@liruilongs.github.io]-[~]└─$
复制代码

将内存页面放置在交换分区中不会损害性能,它可以通过将不需要的页面移出 RAM,以便将 RAM 用于更有用的事情来提高性能。


影响性能的是频繁地将页面移进移出交换区:si和so报告了这一点。一个系统应该有多少互换?人们提出了许多经验法则。如果你仔细想想,你需要交换两个基本的东西。


首先,移动你不需要在内存中所有时间的页面,以便 RAM 可以用于更好的事情。 其次,提供应急储备,避免出现内存不足的情况。空间大小很大程度上取决于系统有多少 RAM 以及你在计算机上做什么。


因为这比经验法则更难计算,人们倾向于根据粗略的估计来设置交换分区。Red Hat 提供的基本指导如下:

系统内存和页缓存进程并不是系统内存的唯一消耗者。内核可以为自己的代码使用内存,或者以其他方式加速系统。


其中一种方法是页缓存。当您运行vmstat或free命令时,即使在拥有大量内存的系统上,也没有多少内存被标记为“空闲”。


如果系统最近执行了大量的存储 I/O,这一点尤其正确。其中一个原因是页缓存。内核使用大部分未分配的内存作为缓存来存储从磁盘读取或写入的数据。


下一次需要数据时,可以从 RAM 而不是磁盘中获取数据。这通常会带来显著的性能改进,因为存储通常比物理内存慢得多。保留少量 RAM 以备短期请求使用;从长远来看,页缓存很容易被释放用于其他用途。

$ free -h              total        used        free      shared  buff/cache   availableMem:            15G        6.2G        316M        801M        9.0G        8.0GSwap:          6.0G        107M        5.9G
复制代码
  • swpd :当前交换到硬盘的内存总量

  • free :未被操作系统或应用程序使用的物理内存总量

  • buff : 系统缓冲区大小(单位为KB),或用于存放等待保存到硬盘的数据的内存大小(单位为KB)。该存储区允许应用程序向Linux内核发出写调用后立即继续执行(而不是等待直到数据被提交到硬盘)

  • cache :用于保存之前从硬盘读取的数据的系统高速缓存或内存的大小(单位为KB)。如果应用程序再次需要该数据,内核可以从内存而非硬盘抓取数据,由此可提高性能

$ vmstat 1 5procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st 1  0 110304 323928     80 9453436    0    0     0     2    0    0  1  1 97  1  0 0  0 110304 322804     80 9453436    0    0     0     0 16190 24907  1  2 96  1  0 0  0 110304 323900     80 9453492    0    0     0     4 15462 24521  1  1 97  1  0 0  0 110304 324232     80 9453492    0    0     0     0 14469 23522  1  1 97  1  0 0  0 110304 324476     80 9453540    0    0     0    16 15790 24329  1  2 95  1  0[iomyw@iomywapp1 ~]$ 

复制代码

温习一下交换空间的创建

交换分区

为你的系统额外添加一个512MiB的交换分区,此交换分区应在系统启动时自动挂载,不删除或以任何方式改动系统上原有的交换分区。

$ fdisk /dev/vdb  #需要分区的硬盘.. ..Command (m for help): n #添加新分区Partition number (2-128, default 2):  # 直接回车(默认)First sector (4194304-20971486, default 4194304):  # 直接回车(默认)Last sector, *sectors or +size{K,M,G,T,P} (4194304-20971486,default 20971486): +512MCreated a new partition 2 of type 'Linux filesystem' and of size 512 MiB.Command (m for help): w  # 保存分区表,并退出The partition table has been altered.Syncing disks.$ partprobe /dev/vdb  # 刷新分区表$ mkswap /dev/vdb2  # 格式化自建分区 vdb2$ vim /etc/fstab/dev/vdb2 swap swap defaults 0 0$ swapon -a  # 启用 fstab 中的交换设备$ swapon -s  # 查看交换分区信息
复制代码

交换文件

创建一个 1G 的交换文件,系统启动时挂载

┌──[root@liruilongs.github.io]-[~]└─$ lsblkNAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTsda      8:0    0  200G  0 disk├─sda1   8:1    0  150G  0 part /└─sda2   8:2    0   10G  0 part [SWAP]┌──[root@liruilongs.github.io]-[/dev]└─$ dd if=/dev/sda1 of=/swap_LRL bs=1024 count=1048576记录了1048576+0 的读入记录了1048576+0 的写出1073741824字节(1.1 GB)已复制,7.93545 秒,135 MB/秒┌──[root@liruilongs.github.io]-[/dev]└─$ mkswap  /swap_LRLmkswap: /swap_LRL: warning: wiping old xfs signature.正在设置交换空间版本 1,大小 = 1048572 KiB无标签,UUID=ddecd087-fa65-4047-860b-609476a942f3
复制代码


┌──[root@liruilongs.github.io]-[/]└─$ vim /etc/fstab┌──[root@liruilongs.github.io]-[/dev]└─$ cat /etc/fstab  | grep LRL/swap_LRL                    swap  swap     defaults        0 0┌──[root@liruilongs.github.io]-[/]└─$ swapon -aswapon: /swap_LRL:不安全的权限 0644,建议使用 0600。┌──[root@liruilongs.github.io]-[/]└─$ swapon -s文件名                          类型            大小    已用    权限/dev/sda2                               partition       10485756        0       -2/swap_LRL                               file    1048572 0       -3
复制代码

调整内核交换的方式

当内核想要释放一页内存时,它需要在两种选择之间进行权衡。它可以从进程内存中交换一个页(放到交换分区),也可以从页缓存中删除一个页。为了做出这个决定,内核将执行以下计算:


swap_tendency = mapped_ratio/2 + distress + vm_swappiness

如果swap_tendency小于 100,内核将从页缓存中回收一个页,如果大于或等于 100,属于进程内存空间的页将符合交换条件。

在这个计算中,

  • mapped_ratio是使用的物理内存的百分比。

  • Distress是衡量内核在释放内存方面有多大困难的指标。它将从 0 开始,但如果需要更多的尝试来释放内存,它将增加(到最大 100)。

  • vm_swappiness 值来自sysctl vm.swappiness即位于内核参数中的一个值,交换分区的采用频率

关于 vm.swappiness 调优。交换空间会严重影响系统。

  • 设置vm.swappiness切换到100,系统几乎总是倾向于换出页面,而不是从页面缓存中回收页面。这将使用更多的内存用于页缓存,这可以大大提高 I/O 繁重工作负载的性能,个人理解有更多内存空间用于 buff 和 cache,所以 I/O。

  • 设置为0将迫使系统尽可能少地进行交换。这可能会使系统响应速度更快,但以牺牲文件系统性能为代价.因为没有更多的内存用过 buff 和 cache,大量的 IO 操作会造成 IO 阻塞。

这里我们简单温习一下,Linux 内核参数如何调整

sysctl -a 查看所有内核参数:

┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ sysctl -a  # 查看所有调优参数abi.vsyscall32 = 1crypto.fips_enabled = 0debug.exception-trace = 1debug.kprobes-optimization = 1debug.panic_on_rcu_stall = 0dev.hpet.max-user-freq = 64..............
复制代码

cat 根据变量找对应参数文件

┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ sysctl -a | grep net.ipv4.ip_forwardnet.ipv4.ip_forward = 1net.ipv4.ip_forward_use_pmtu = 0┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ cd ../net/ipvipv4/ ipv6/┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ cd ../net/ipv4/┌──[root@liruilongs.github.io]-[/proc/sys/net/ipv4]└─$ cat ip_forward1
复制代码

设置内核参数:临时调整

/proc 目录下的数据是存放在内存中数据,每次重启就没了。

┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ cat swappiness30┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ echo 40 > swappiness  ## 临时调整┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ cat swappiness40
复制代码

设置调优参数:永久调整

┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ echo "vm.swappiness = 20" >>  /etc/sysctl.conf  ## 永久调整┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ sysctl -pnet.ipv4.ip_forward = 1vm.swappiness = 20┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$
复制代码

下面是 vm.swappiness 参数修改后 vmstat 的两个输出示例。

  • 第一个图显示了在内存压力下更倾向于交换的系统

  • 第二个图显示了更倾向于收缩页缓存的系统

交换分区和文件调优

交换分区性能在很大程度上受到交换分区的位置和数量的影响。

  • 在旋转的硬盘驱动器(机械硬盘)上,由于ZCAV效应,将交换分区放置在盘片的外缘比将其放置在中心附近将提供更好的吞吐量

  • SSD存储(固态硬盘)上放置交换空间可能会由于其较低的延迟和较高的吞吐量而导致更好的性能。

需要注意的是,如果您使用 SSD 作为交换分区,并且交换分区经常使用,那么您应该确保您的设备支持适当的磨损均衡(基于 SLC-的存储可能优于基于 MLC 的存储,SLC 的特点是寿命长,同样规格的 MLC 寿命比 SLC 要低,性能则相反)否则,设备可能会过早磨损。

使用mkswap创建多个交换空间。它们可能基于磁盘分区文件。由于内核映射交换文件的方式,只要交换文件没有碎片化交换文件和交换分区的性能应该大致相似

当使用多个交换分区时,可以使用挂载选项pri=value来指定每个空间的使用优先级。

$ cat /etc/fstab |grep -i swap|grep -i mpath/dev/mapper/mpath18        swap                    swap    sw,pri=60        0 0
复制代码
  • 与较低数字的交换分区将首先被填满,然后再移动到较高的数字。通过这种方式,更快的磁盘可以优先于较慢的磁盘

  • 当以相同的优先级激活多个交换分区时,将以轮询方式使用它们,从而减少每个交换分区的访问次数,从而获得更好的性能。

内存回收

Linux 物理内存需要不时地回收,以防止内存被填满,从而导致系统不可用。

脏内存和非活动内存回收

在我们了解内存是如何回收的之前,我们必须首先了解内存页可能处于的不同状态。这些不同的状态是:

  • Free: 页面可以立即分配,空闲的内存;

  • inactive Clean: 该页处于非活动使用状态,其内容与磁盘上的内容相对应,因为它已经被回写或自读取以来没有更改

  • inactive Dirty: 该页不是活跃使用,但该页的内容已经被修改,从磁盘读取后,还没有写回

  • Active: 该页面正在活跃使用中,并且不是被释放的候选页面


当需要分配新页面时,标记为 inactive Clean 的页面可以被视为空闲页面,但是如果拥有该页面的进程以后再次需要它,就会发生重大的页面错误


通过 /proc/meminfo 可以获得系统范围内内存分配的概览。我们可以处理的是Inactive(file)Dirty:。

$ cat /proc/meminfo MemTotal:       33011552 kBMemFree:        19579840 kBMemAvailable:   22517876 kBBuffers:             304 kBCached:          3497240 kBSwapCached:        35568 kBActive:          8894452 kBInactive:        2717480 kBActive(anon):    7107136 kBInactive(anon):  1869124 kBActive(file):    1787316 kBInactive(file):   848356 kB....................Dirty:               716 kB....................
复制代码
  • Inactive(file):可以回收而不会对性能产生巨大影响的页面缓存内存,free 命令中 cache 部分的回收

  • Dirty:等待写回磁盘的内存,free 命令中 buff 部分的回收

匿名页面Inactive(anon)(与磁盘上的文件没有关联的那些页面)不能轻易释放,需要将其交换到磁盘以释放它们。


Inactive(file)Dirty的强制回收需要修改内核参数vm.drop_caches = 0

$ sysctl  -a | grep drop_cachesvm.drop_caches = 0
复制代码

手动执行 sync 命令(sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件)

┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ cat drop_caches  #缓存处理0┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ sync
复制代码

通过执行 echo 3 > /proc/sys/vm/drop_caches的方式清理,具体参数含义

To free pagecache: echo 1 > /proc/sys/vm/drop_cachesTo free reclaimable slab objects (includes dentries and inodes): echo 2 > /proc/sys/vm/drop_cachesTo free slab objects and pagecache: echo 3 > /proc/sys/vm/drop_caches
复制代码


┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ free -m              total        used        free      shared  buff/cache   availableMem:           3935         212        3357          16         366        3440Swap:         10239           0       10239┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ echo 3 > /proc/sys/vm/drop_caches┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$ free -m              total        used        free      shared  buff/cache   availableMem:           3935         200        3575          16         159        3504Swap:         10239           0       10239┌──[root@liruilongs.github.io]-[/proc/sys/vm]└─$
复制代码

脏页写入调优

对于每个进程视图,你可以使用/proc/pid/maps.这个文件详细说明了分配给进程的每个内存段,包括共享/私有干净内存脏内存的大小。为了解析它,我们可以编写一个小的 awk 程序,如下所示:

cat /proc/$$/smaps | awk  '/Shared_Clean/{SHCL+=$2}/Shared_Dirty/{SHDT+=$2}/Private_Clean/{PRCL+=$2}/Private_Dirty/{PRDT+=$2}  END{   print "Total Clean:",SHCL +PRCL    print "Total Dirty:",SHDT  +PRDT}'
复制代码


┌──[root@liruilongs.github.io]-[/dev]└─$ cat /proc/$$/smaps | awk  '> /Shared_Clean/{SHCL+=$2}> /Shared_Dirty/{SHDT+=$2}> /Private_Clean/{PRCL+=$2}> /Private_Dirty/{PRDT+=$2}> END{>   print "Total Clean:",SHCL +PRCL>   print "Total Dirty:",SHDT  +PRDT> }'Total Clean: 1808Total Dirty: 1536
复制代码

必须将脏页写入磁盘,以防止内存被无法释放的页填满。由于内存是易失的(断电时内容会丢失),脏页也需要写入磁盘,以防止断电时数据丢失。


在内核中,将脏页写入磁盘是由per-BDI flush线程处理的,这些线程将在必要时创建。Per-BDI flush线程将在进程列表中显示为flush-MAJOR: MINOR


有几个内核参数控制per-BDI刷新线程何时开始将数据写入磁盘。这样内核就不会因为某个进程修改了另一个字节的内存而连续地多次写入同一个页面。

┌──[root@liruilongs.github.io]-[~]└─$ sysctl  -a | grep dirty_vm.dirty_background_bytes = 0vm.dirty_background_ratio = 10vm.dirty_bytes = 0vm.dirty_expire_centisecs = 3000vm.dirty_ratio = 20vm.dirty_writeback_centisecs = 1500┌──[root@liruilongs.github.io]-[~]└─$
复制代码

vm.dirty_ratio = 20 :产生写操作的进程整个系统内存中处于脏状态的百分比,是绝对的脏数据限制,内存里的脏数据百分比不能超过这个值,如果超过将阻塞IO直到写出脏页


vm.dirty_background_ratio = 10 :系统总内存和脏页的百分比,即内存可以填充“脏数据”的百分比。在这个百分比事开始,内核会在后台开始写数据


vm.dirty_bytes = 0vm.dirty_background_bytes = 0 这两个参数为上面参数的 byte 单位时的值


vm.dirty_expire_centisecs = 3000:脏数据在符合写入磁盘条件之前必须有多旧(以 1/100 秒为单位),即可以存活多久。这样内核就不会因为某个进程修改了一个字节的内存而连续地多次写入同一个页面。

vm.dirty_writeback_centisecs = 1500内核唤醒刷新线程pdflush/flush/kdmflush以写入数据的频率(1/100 秒)。设置为 0 将完全禁周期性回写


大多数调优配置文件至少修改上述设置之一。调优规则可以遵循下面的策略

  • 设置较低的比率将导致更频繁但更短的写操作,这适合交互式系统

  • 设置较高的比率将导致更少但更大的写操作,导致总体开销更小,但可能导致交互式应用程序响应时间更长.适合执行非交互的 I/O 任务处理,比如大文件生成之类。

内存不足处理和“OOM killer(内存杀手)”

当脏页的数据太多,同时没有可用的页面时,内核试图回收内存来满足请求。如果不能及时回收足够的内存,就会出现内存不足OOM的情况。


对于系统的级别的 OOM,默认情况下,系统将启动OOM killer,选择并杀死一个或多个进程以释放内存,以便满足请求。具体的记录日志是在/var/log/messages中,如果出现了Out of memory字样,说明系统曾经出现过 OOM,


在 Linux 内核参数中,我们可以通过vm.panic_on_oom参数来设置遇到 OOM 的情况,启动OOM killer的策略


如果内核参数sysctl vm.panic_on_oom设置为1而不是0,内核将会发生panic,即直接摆烂,什么时候挂掉算什么时候。默认为 0.即自动启动OOM killer

┌──[root@liruilongs.github.io]-[~]└─$ sysctl vm.panic_on_oomvm.panic_on_oom = 0
复制代码

出现内存不足的情况,就没有很多合理的恢复选项。终止进程以释放内存、放弃并终止系统或死锁都是可能的选择。


为了确定 OOM 杀手应该杀死哪个进程,内核为每个进程保持一个运行不良评分,可以在/proc/pid/oom_score中查看。

systemd 进程的值

[root@ecs-liruilong ~]# cat /proc/1/oom_score0
复制代码

分数越高,进程越有可能被 OOM 杀手杀死。许多因素被用来计算这个分数:

  • VM 大小(不是 RSS 大小),

  • 进程所有子进程的累积 VM 大小,

  • nice 值(正的 nice 值会给出更高的分数),

  • 总运行时间(较长的总运行时间会降低分数),

  • 运行用户(根进程会得到轻微的保护),

  • 进程执行直接硬件访问,分数也会降低。

  • 内核本身和 PID1 (sysemd)是免疫的 OOM 杀手。


可调的/proc/PID/oom_adj可以用来手动调整oom_score。配置该 pid 进程被 oom killer 杀掉的权重oom_adj可以的值从-17 到 15,其中 0 表示不改变(默认),越高的权重,意味着更可能被 oom killer 选中,-17 表示免疫(永远不会杀死)。

[root@ecs-liruilong ~]# cat /proc/1/oom_adj0
复制代码

如果你希望强制的执行OOM Killer

可以echo f > /proc/sysrq-trigger,但请记住,至少会有一个进程被杀死。

[root@ecs-liruilong ~]# echo f > /proc/sysrq-trigger
Message from syslogd@ecs-liruilong at Aug  1 14:32:18 ... kernel:[340648.118967] Kernel panic - not syncing: Out of memory: system-wide panic_on_oom is enabled
复制代码

输出将被发送到 dmesg。

[root@ecs-liruilong ~]# cat /var/log/dmesg
复制代码

博文参考

发布于: 2022 年 08 月 01 日阅读数: 83
用户头像

InfoQ写作平台签约作者,RHCE、CKA认证 2022.01.04 加入

Java 后端一枚,技术不高,前端、Shell、Python 也可以写一点.纯种屌丝,不热爱生活,热爱学习,热爱工作,喜欢一直忙,不闲着。喜欢篆刻,喜欢吃好吃的,喜欢吃饱了晒太阳。

评论

发布
暂无评论
Linux性能调优之内存负载调优的一些笔记_签约计划第三季_山河已无恙_InfoQ写作社区