liuhao163.github.io

杂七杂八


  • Home

  • Categories

  • Tags

  • Archives

  • Sitemap

系统优化-IO-IO问题导致的接口延迟过高

Posted on 2020-05-13 | Edited on 2022-09-21 | In 性能优化 , I/O

案例准备:启动一个web应用,用另一个中端请求,发现响应极慢

1
docker run --name=app -p 10000:80 -itd feisky/word-pop
1
curl http://192.168.0.10:1000/popularity/word

top、iostat发现磁盘的io很高,用strace的套路查看代码发现没有write的痕迹,这时候我们可以用bcc-tools下的filetop查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 切换到工具目录
$ cd /usr/share/bcc/tools

# -C 选项表示输出新内容时不清空屏幕
$ ./filetop -C

TID COMM READS WRITES R_Kb W_Kb T FILE
514 python 0 1 0 2832 R 669.txt
514 python 0 1 0 2490 R 667.txt
514 python 0 1 0 2685 R 671.txt
514 python 0 1 0 2392 R 670.txt
514 python 0 1 0 2050 R 672.txt

...

TID COMM READS WRITES R_Kb W_Kb T FILE
514 python 2 0 5957 0 R 651.txt
514 python 2 0 5371 0 R 112.txt
514 python 2 0 4785 0 R 861.txt
514 python 2 0 4736 0 R 213.txt
514 python 2 0 4443 0 R 45.txt

我们可以看到 线程ID,命令,读/写的次数、大小,文件类型,文件名称找到了创建了xxx.txt文件,我们通过ps找到tid所属的进程

1
ps efT|grep 514

在通过bcc下的opensnoop找到具体的文件

1
2
3
4
$ opensnoop
12280 python 6 0 /tmp/9046db9e-fe25-11e8-b13f-0242ac110002/650.txt
12280 python 6 0 /tmp/9046db9e-fe25-11e8-b13f-0242ac110002/651.txt
12280 python 6 0 /tmp/9046db9e-fe25-11e8-b13f-0242ac110002/652.txt

猜测是程序接到请求后,创建了一堆临时文件,在读取到内存,然后删除了文件导致的,修改程序问题解决。

系统优化-IO-写log引起的问题IO问题

Posted on 2020-05-12 | Edited on 2022-09-21 | In 性能优化 , I/O

以下场景是一个典型的由于写log导致IO问题的场景。

注:我的测试环境是1c2g内存的虚拟机,所以无法模拟案例,案例的需求是2c8g,所以这里只罗列除了排查思路。

准备工作

1
docker run -v /tmp:/tmp --name=app -itd feisky/logapp

启动后发现系统响应变慢

1
2
3
4
5
6
7
8
9
10
11
12
# 按1切换到每个CPU的使用情况
$ top
top - 14:43:43 up 1 day, 1:39, 2 users, load average: 2.48, 1.09, 0.63
Tasks: 130 total, 2 running, 74 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.7 us, 6.0 sy, 0.0 ni, 0.7 id, 92.7 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.3 sy, 0.0 ni, 92.3 id, 7.3 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8169308 total, 747684 free, 741336 used, 6680288 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 7113124 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18940 root 20 0 656108 355740 5236 R 6.3 4.4 0:12.56 python
1312 root 20 0 236532 24116 9648 S 0.3 0.3 9:29.80 python3
  1. cpu0 的wa已经到了92.7
  2. pyhton占了6.3的是你用率
  3. 内存的buff/cache占了6个g

    综上觉得是io的问题,pyhton这个进程导致的,内存的占用有可能是因为大量的io读写文件导致的

    用iostat查看下io的情况

1
2
3
4
5
6
# -d表示显示I/O性能指标,-x表示显示扩展统计(即所有I/O指标)
$ iostat -x -d 1
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
loop0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda 0.00 64.00 0.00 32768.00 0.00 0.00 0.00 0.00 0.00 7270.44 1102.18 0.00 512.00 15.50 99.20

系统sda的写到了32mb每秒,w_waite到了7s,私用率几乎100%,进一步印证了才想

下面我们该用pidstat查看具体时间那个进程导致的了,这里看到是18904-python导致的

1
2
3
4
5
6
7
8
9
$ pidstat -d 1

15:08:35 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
15:08:36 0 18940 0.00 45816.00 0.00 96 python

15:08:36 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
15:08:37 0 354 0.00 0.00 0.00 350 jbd2/sda1-8
15:08:37 0 18940 0.00 46000.00 0.00 96 python
15:08:37 0 20065 0.00 0.00 0.00 1503 kworker/u4:2

strace -p pid来查看源码,发现程序会给句柄为3的文件写入300mb的数据,stat(“/tmp/logtest.txt.1”,找到了文件所在

1
2
3
4
5
6
7
8
9
10
11
12
$ strace -p 18940
strace: Process 18940 attached
...
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f7aee9000
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f682e8000
write(3, "2018-12-05 15:23:01,709 - __main"..., 314572844
) = 314572844
munmap(0x7f0f682e8000, 314576896) = 0
write(3, "\n", 1) = 1
munmap(0x7f0f7aee9000, 314576896) = 0
close(3) = 0
stat("/tmp/logtest.txt.1", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0

我们可以用lsof -p进一步印证是哪些文件

1
2
3
4
5
6
7
8

$ lsof -p 18940
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python 18940 root cwd DIR 0,50 4096 1549389 /
python 18940 root rtd DIR 0,50 4096 1549389 /
…
python 18940 root 2u CHR 136,0 0t0 3 /dev/pts/0
python 18940 root 3w REG 8,1 117944320 303 /tmp/logtest.txt

系统优化-IO-运行原理和重要的指标

Posted on 2020-05-06 | Edited on 2022-09-21 | In 性能优化 , I/O

磁盘

按照存储介质来来分类:

  • 机械硬盘HDD:包含磁头和磁片,读取数据通过改变磁头和磁道来进行数据的读取,随机IO因为寻址和改变磁道性能很差,在IO压力比较大的场景要尽量避免随机IO。
  • 固态硬盘SSD:由电子元件组成,不需要磁道读取,所以随机IO和顺序IO性能都要比机械硬盘好得多。

    但是,固态硬盘的随机IO因为也遵循擦除、写入所以随机IO会引起大量的垃圾回收,并且无法用到预读性能也不高。、

    此外,机械磁盘和固态磁盘还分别有一个最小的读写单位。机械磁盘的最小读写单位是扇区,一般大小为 512 字节。而固态磁盘的最小读写单位是页,通常大小是 4KB、8KB 等。

    按照接口来分类:比如可以把硬盘分为 IDE(Integrated Drive Electronics)、SCSI(Small Computer System Interface) 、SAS(Serial Attached SCSI) 、SATA(Serial ATA) 、FC(Fibre Channel) 等。

    IDE:后缀一般为hd
    SCSI或者SATA:后缀一般为sd

    根据设备编号后面一般跟小写字母a、b、c,我们在设备上还可以分区用0~9数字表示比如:/dev/sda1

通用块层

和VFS类似在文件系统到驱动程序之间实现了通用块层,俩个作用:

  1. 向上为程序和文件系统提供统一的访问接口,向下屏蔽个文件系统程序之间的差异,提供统一框架管理各系统的驱动程序
  2. 实现IO的调度一般有:NONE、NOOP、CFQ、DeadLine
    1. NONE ,更确切来说,并不能算 I/O 调度算法。因为它完全不使用任何 I/O 调度器,对文件系统和应用程序的 I/O 其实不做任何处理,常用在虚拟机中(此时磁盘 I/O 调度完全由物理机负责)。
    2. NOOP ,是最简单的一种 I/O 调度算法。它实际上是一个先入先出的队列,只做一些最基本的请求合并,常用于 SSD 磁盘。、
    3. CFQ(Completely Fair Scheduler),也被称为完全公平调度器,是现在很多发行版的默认 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求。类似于进程 CPU 调度,CFQ 还支持进程 I/O 的优先级调度,所以它适用于运行大量进程的系统,像是桌面环境、多媒体应用等。、
    4. DeadLine 调度算法,分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量,并确保达到最终期限(deadline)的请求被优先处理。DeadLine 调度算法,多用在 I/O 压力比较重的场景,比如数据库等。

I/O栈

如图:

avator

  • 文件系统层:对上为程序提供统一访问接口,对下会通过通用块层,来存储和管理磁盘数据。
  • 通用块层:包括块设备I/O队列和I/O调度器,负责I/O请求的排队、合并等调度工作
  • 设备层,包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。

常用的查看工具

我们主要关注的几个指标

  • 使用率:如果io的使用率过高,表示系统存在瓶颈。
  • 饱和度:是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
  • 请求数/S:是指每秒的 I/O 请求数。
  • 吞吐:是指每秒的 I/O 请求大小。
  • 响应时间:是指 I/O 请求从发出到收到响应的间隔时间。

iostat

1
2
3
4
5
# -d -x表示显示所有磁盘I/O的指标
$ iostat -d -x 1
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sdb 0.00 0.04 0.56 1.74 29.28 50.93 69.51 0.00 1.28 0.21 1.63 0.04 0.01
sda 0.00 0.67 0.03 1.74 0.25 28.85 32.91 0.00 0.04 0.13 0.04 0.02 0.00

图内指标如下:

avator

这些指标中,你要注意:

  • %util ,就是我们前面提到的磁盘 I/O 使用率;
  • r/s+ w/s ,就是 IOPS;
  • rkB/s+wkB/s ,就是吞吐量;
  • r_await+w_await ,就是响应时间。

    在观测指标时,也别忘了结合请求的大小( rareq-sz 和 wareq-sz)一起分析。你可能注意到,从 iostat 并不能直接得到磁盘饱和度。事实上,饱和度通常也没有其他简单的观测方法,不过,你可以把观测到的,平均请求队列长度或者读写请求完成的等待时间,跟基准测试的结果(比如通过 fio)进行对比,综合评估磁盘的饱和情况。

iotop

类似于top可以查看整体的io情况

pidstat

如果想查看进程的io情况我们要用pidstat加参数-d

1
2
3
4
5
6
# pidstat -d
03:02:23 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
03:02:23 PM 0 1 145.77 25.49 0.72 systemd
03:02:23 PM 0 30 0.00 0.00 0.00 kswapd0

# kB_ccwr/s 是每秒取消的写请求

系统优化-IO-基础概念

Posted on 2020-05-04 | Edited on 2022-09-21 | In 性能优化 , I/O

在讲内存的时候我们提到了buffer用于加速磁盘的读写,cache用于加速文件的读写,那么磁盘和文件它们俩个的区别是什么呢?linux的文件系统的机制又是什么?

磁盘和文件系统

  • 磁盘:是块设备,用于数据的持久化;
  • 文件系统:在磁盘基础上提供管理文件的树状结构;

    文件系统为每个文件都分配了俩种数据结构:

  • 索引节点:【inode】记录文件的原信息,如:数据位置、修改时间、权限等

  • 目录项:【dentry】,多个目录项组成的树状结构就是我们的文件系统,保存,父节点、子节点其他目录项的关联关系、文件名称、inode地址等信息,多个目录项组成了文件树

    整个文件系统由目录项、超级块、索引块、数据块四部分组成,他们的关系如图:

avator

其中,磁盘读写的最小单位是扇区,然而扇区只有512B大小,如果每次都读写这么小的单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块,然后每次都以逻辑块为最小单元,来管理数据。常见的逻辑块大小为 4KB,也就是由连续的 8 个扇区组成。

目录项是缓存在内存中。
索引项是记录磁盘的信息的但是,CPU为了弥补磁盘和内存的访问差异,会将inode也缓存到内存的cache页中。

虚拟文件系统VFS

Liunx在各种文件系统的实现上定义了一层虚拟文件系统【VFS】,它定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,只需要通过VFS提供的统一接口和磁盘进行交互,而不需要再关心底层各种文件系统的实现细节。

  • 磁盘文件系统:也就是把数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4、XFS、OverlayFS 等,都是这类文件系统。
  • 内存文件系统:这类文件系统,不需要任何磁盘分配存储空间,但会占用内存。我们经常用到的 /proc 文件系统,其实就是一种最常见的虚拟文件系统。此外,/sys 文件系统也属于这一类,主要向用户空间导出层次化的内核对象。
  • 网络文件系统:也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等

文件系统的I/O

  • 是否利用标准库缓存的I/O:标准库为了加速I/O的访问效率,会实现一层缓冲,在进行系统调用,比如java里的各种BufferedStream。注意这里的缓冲是标准库实现的
  • 是否利用内存缓存I/O:我们I/O的系统调用,默认都会用到内存的cache页和buffer页,如果采用直接I/O,在I/O操作时候要使用O_DIRECT。
  • 阻塞和非阻塞I/O:如果是阻塞I/O是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务;是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。访问管道或者网络套接字时,设置O_NONBLOCK标志,就表示用非阻塞方式访问;而如果不做任何设置,默认的就是阻塞访问。
  • 同步I/O和异步I/O:是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应。是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。

    举个例子,在操作文件时,如果你设置了 O_SYNC 或者 O_DSYNC 标志,就代表同步 I/O。如果设置了 O_DSYNC,就要等文件数据写入磁盘后,才能返回;而 O_SYNC,则是在 O_DSYNC 基础上,要求文件元数据也要写入磁盘后,才能返回。
    再比如,在访问管道或者网络套接字时,设置了 O_ASYNC 选项后,相应的 I/O 就是异步 I/O。这样,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写。

如何查看I/O

我们可以采用df命令

1
2
3
$ df -h /dev/sda1
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 29G 3.1G 26G 11% /

这里的数据是不包括inode的信息的,所以会出现一种情况,明明我们的文件系统还有很大的空间,但是却提示磁盘空间不足了,原因就在于inode是会写入磁盘的,很有可能是小文件太多inode占用了太大的空间,我们可以铜鼓df的-i参数查看inode的信息

如果想查看inode信息

1
2
3
$ df -i /dev/sda1
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 3870720 157460 3713260 5% /

缓存

我们CPU为了加速I/O的性能,会将的目录项和索引节点加载到内存中,他们处于内存的内核空间由slab控制,可以通过/proc/slabinfo查看

1
2
3
4
5
6
7
8
9
10
$ cat /proc/slabinfo | grep -E '^#|dentry|inode'
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
xfs_inode 0 0 960 17 4 : tunables 0 0 0 : slabdata 0 0 0
...sh
ext4_inode_cache 32104 34590 1088 15 4 : tunables 0 0 0 : slabdata 2306 2306 0hugetlbfs_inode_cache 13 13 624 13 2 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 1190 1242 704 23 4 : tunables 0 0 0 : slabdata 54 54 0
shmem_inode_cache 1622 2139 712 23 4 : tunables 0 0 0 : slabdata 93 93 0
proc_inode_cache 3560 4080 680 12 2 : tunables 0 0 0 : slabdata 340 340 0
inode_cache 25172 25818 608 13 2 : tunables 0 0 0 : slabdata 1986 1986 0
dentry 76050 121296 192 21 1 : tunables 0 0 0 : slabdata 5776 5776 0

主要查看inode_cache、proc_inode_cache、dentry大小。

注意我们如果用下面命令查找文件的时候因为开始没有缓存,会增加dentry、inode_cache、proc_inode_cache用量。

1
find / --name <filename>

系统优化-内存-阶段性总结

Posted on 2020-05-04 | Edited on 2022-09-21 | In 性能优化 , 内存

本文主要是针对之前的内存方面的学习进行阶段性的总结,重点在于我们去看内存的哪些重要指标,常用工具是哪些,排查内存问题的思路。

重要指标

如下的脑图:

avator

系统内存:

  • 已用内存和剩余内存很容易理解,就是已经使用和还未使用的内存。
  • 剩余内存:只进程可以分配的内存。
  • 缺页异常:系统在分配内存,会产生缺页异常,当访问到该内存地址发现异常会切换到内存完成真正的物理内存分配
    • 主缺页异常(majflt):从磁盘中加载分配(Swap)
    • 次缺页异常(minflt):直接从内存中分配
  • 共享内存:通过 tmpfs 实现的,所以它的大小也就是 tmpfs 使用的内存大小。tmpfs 其实也是一种特殊的缓存。
  • 缓存包括两部分,一部分是磁盘读取文件的页缓存,用来缓存从磁盘读取的数据,可以加快以后再次访问的速度。另一部分,则是 Slab 分配器中的可回收内存。

    程序的内存:

  • VIRT:包括了进程代码段、数据段、共享内存、已经申请的堆内存和已经换出的内存等。这里要注意,已经申请的内存,即使还没有分配物理内存,也算作虚拟内存。

  • RSS:进程实际使用的物理内存,不过它不包括Swap和共享内存。
  • PSS:进程自己独自占有的+共享的/共享的数目。
  • USS:进程独自占用的物理内存(不包含共享库占用的内存)。

    Swap

  • 用量

  • 换入/换出

常用工具

性能指标和工具的联系,如图1

avator

从性能工具出发,整理了这些常见工具能提供的内存指标。,如图2

avator

排查思路

常见的如:

  • 内存泄露:内存持续增长直至进程被Kill,可以用memleak排查,然后修改代码
  • Swap过高:查看用vmstat、sar、top先看换入换出的情况,然后调整swapiness或者关闭swap大内存场景建议关闭swap
  • buffer/cache的优化:可以用cachestat和cachetop看缓存的命中率,如果过低,去相关代码排查
  • 内存占用过高:可以用pmap或者smap找到内存分配的地方。

系统优化-内存-Swap机制

Posted on 2020-05-03 | Edited on 2022-09-21 | In 性能优化 , 内存

内存回收

之前提到当linux的内存不足时候会通过oom机制来kill程序加速释放内存,除了这种方法,linux还可以在资源不足时候释放可以回收的内存。哪些是可回收的内存呢?

  • buffer/cache:这些被成为文件页(File-backed Page)大部分文件页可以直接回收,需要了在从磁盘读取,如果系统只修改了内存还没有写入到磁盘,我们成为脏页,需要写回磁盘在就进行回收
    • 可以在应用程序中,通过系统调用 fsync ,把脏页同步到磁盘中;
    • 也可以交给系统,由内核线程 pdflush 负责这些脏页的刷新;
  • 应用程序动态分配的内存,我们称之为匿名页(Anonymous Page),会通过Swap换入/换出的方式将内存写入到磁盘中来释放内存。

Swap的机制介绍

swap将补偿访问的内存通过换入将内存数据写到磁盘中,需要了再从磁盘读取这实际上是一种用时间换空间的思想。

回收方式

  1. 直接回收:大部分动态分配的内存都可以直接回收
  2. kswapd0:一个专门的内核线程用来定期回收内存。为了衡量内存的使用情况,kswapd0定义了三个内存阈值(watermark,也称为水位),分别是页最小阈值(page min),页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)。剩余内存,则使用 pages_free 表示。这里,我画了一张图表示它们的关系。

    avator

    kswapd0的运行机制如下:

    小于pages_min:内存页耗尽
    小于pages_low:内存页不足,kswapd0 会执行内存回收,直到剩余内存大于高阈值为止
    小于pages_high:内存资源还可以满足申请的需求
    大于pages_high:内存资源充足

    我们可以通过修改/proc/sys/vm/min_free_kbytes来修改pages_min,其他的pages_low和pages_high都是通过pages_min计算出来的。

NUMA 与 Swap

NUMBA是(Non-Uniform Memory Access),多个cpu会被划分到不通的node中,我们可以用numbactl看到如下的情况,只有一个node,对应了0,1俩个cpu,有7977MB的内存,空余内存是4416MB。

1
2
3
4
5
6
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...

实际上,前面提到的三个内存阈值(页最小阈值、页低阈值和页高阈值),都可以通过内存域在 proc 文件系统中的接口 /proc/zoneinfo 来查看。比如,下面就是一个 /proc/zoneinfo 文件的内容示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

$ cat /proc/zoneinfo
...
Node 0, zone Normal
pages free 227894
min 14896 ## 三个水位线
low 18620
high 22344
...
##空闲page和上面的free对应
nr_free_pages 227894
##活跃和非活跃的匿名页数
nr_zone_inactive_anon 11082
nr_zone_active_anon 14024
##活跃和非活跃的文件页数
nr_zone_inactive_file 539024
nr_zone_active_file 923986
...

内存资源紧张时候,默认采用回收匿名页数以及文件页数的方式来回收内存,可以通过/proc/sys/vm/zone_reclaim_mode调整

  • 默认的 0 ,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。
  • 1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以用 Swap 方式回收内存。

swappiness

内存回收有俩种方式

  1. 回收文件页:通过fsync将buffer和cache写入到磁盘来释放这一部分内存
  2. 回收匿名页:通过swap来释放匿名页内存

    Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。

    注意swappiness只是一个趋势。

案例排查

首先确认swap开启,通过free确认,如果没开启可用下面方法开启

1
2
3
4
5
6
7
8
9

# 创建Swap文件
$ fallocate -l 8G /mnt/swapfile
# 修改权限只有根用户可以访问
$ chmod 600 /mnt/swapfile
# 配置Swap文件
$ mkswap /mnt/swapfile
# 开启Swap
$ swapon /mnt/swapfile

测试方案:用命令dd

1
2
# 写入空设备,实际上只有磁盘的读请求
$ dd if=/dev/sda1 of=/dev/null bs=1G count=2048

这时候我们用sar -r命令观察,我们发现kbmemfree值越来越少,%memused越来越大,且kbbuffers越来越大,因为dd直接和磁盘交互,符合预期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 间隔1秒输出一组数据
# -r表示显示内存使用情况,-S表示显示Swap使用情况
$ sar -r -S 1
04:39:56 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:57 6249676 6839824 1919632 23.50 740512 67316 1691736 10.22 815156 841868 4

04:39:56 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:39:57 8388604 0 0.00 0 0.00

04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:58 6184472 6807064 1984836 24.30 772768 67380 1691736 10.22 847932 874224 20

04:39:57 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:39:58 8388604 0 0.00 0 0.00

…

04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:44:07 152780 6525716 8016528 98.13 6530440 51316 1691736 10.22 867124 6869332 0

04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:44:07 8384508 4096 0.05 52 1.27
  • 刚开始,剩余内存(kbmemfree)不断减少,而缓冲区(kbbuffers)则不断增大,由此可知,剩余内存不断分配给了缓冲区。
  • 一段时间后,剩余内存已经很小,而缓冲区占用了大部分内存。这时候,Swap 的使用开始逐渐增大,缓冲区和剩余内存则只在小范围内波动。

为什么缓冲区只在小范围内波动?由哪些进程导致,这时候我们用cachetop进行进一步排查发现是dd导致的,他的READ_HIT只有50%命中率且Miss的page已经到了41022页,可见也符合我们猜测的预期。

1
2
3
4
5
$ cachetop 5
12:28:28 Buffers MB: 6349 / Cached MB: 87 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
18280 root python 22 0 0 100.0% 0.0%
18279 root dd 41088 41022 0 50.0% 50.0%

为什么Swap开始升高了?不是应该释放buffer么?我们查看/proc/zoneinfo返现,内存小于low,内存就开始执行回收才做恢复到high附近,周而复始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# -d 表示高亮变化的字段
# -A 表示仅显示Normal行以及之后的15行输出
$ watch -d grep -A 15 'Normal' /proc/zoneinfo
Node 0, zone Normal
pages free 21328
min 14896
low 18620
high 22344
spanned 1835008
present 1835008
managed 1796710
protection: (0, 0, 0, 0, 0)
nr_free_pages 21328
nr_zone_inactive_anon 79776
nr_zone_active_anon 206854
nr_zone_inactive_file 918561
nr_zone_active_file 496695
nr_zone_unevictable 2251
nr_zone_write_pending 0

我们发现有时候释放的是未知页即直接内存有时候是文件页即buffer,这是由于我们swappiness配置导致的

1
2
cat /proc/sys/vm/swappiness
60

系统优化-内存-常用的内存泄露的排查方案

Posted on 2020-05-02 | Edited on 2022-09-21 | In 性能优化 , 内存

linux动态分配内存经常会遇到俩个问题:

  1. 内存没有及时回收,发生内存泄露
  2. 访问的是已分配边界外的内存,造成内存越界

这章我们来讲下内存泄露、以及排查内存泄露的方法

内存分配以及回收

我们的内存主要分为以下部分、堆、栈、只读端、数据段、内存映射段。

  • 栈:主要存放局部变量,由系统统一管理,所以不需要主动回收内存,一旦程序的作用域超过局部变量,就需要在堆中动态分配内存,在栈中的内存不会导致内存泄露
  • 堆:程序通过malloc申请内存,除非程序退出否则申请的内存不会释放。这部分内存会导致内存泄露
  • 只读段:保存程序的代码和常量,由于不需要继续申请内存,这部分会导致引起内存泄露
  • 数据段:保存静态变量和全局变量,由于申请时候已经知道了大小所以也不需要释放,这部分会导致引起内存泄露
  • 内存映射段:保存动态链接库和共享内存,由于共享内存的存在,

    综上,堆、和内存映射段会导致内存泄露

    内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。内存泄漏不断累积,甚至会耗尽系统内存。虽然oom会Kill程序,但是在kill程序前会有很多严重的问题

如何定位内存泄露

我们可以使用bcc-tools下的memleak,如图下面就是一个简单的内存泄露场景,child会分配内存而且没有回收

1
2
3
4
5
6
7
8
9
10
11
12
13
$ docker cp app:/app /app
$ /usr/share/bcc/tools/memleak -p $(pidof app) -a
Attaching to pid 12512, Ctrl+C to quit.
[03:00:41] Top 10 stacks with outstanding allocations:
addr = 7f8f70863220 size = 8192
addr = 7f8f70861210 size = 8192
addr = 7f8f7085b1e0 size = 8192
addr = 7f8f7085f200 size = 8192
addr = 7f8f7085d1f0 size = 8192
40960 bytes in 5 allocations from stack
fibonacci+0x1f [app]
child+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]

解决后的如下面所示

1
2
3
4
5
# 重新执行 memleak工具检查内存泄漏情况
$ /usr/share/bcc/tools/memleak -a -p $(pidof app)
Attaching to pid 18808, Ctrl+C to quit.
[10:23:18] Top 10 stacks with outstanding allocations:
[10:23:23] Top 10 stacks with outstanding allocations:

由mMemleak在centos中需要内核4.1以上支持,所以在老系统中我们用到valgrind的memcheck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 valgrind  --tool=memcheck --leak-check=summary ./xxx-api

==20474==
==20474== HEAP SUMMARY:
==20474== in use at exit: 4,064 bytes in 8 blocks
==20474== total heap usage: 16 allocs, 8 frees, 4,288 bytes allocated
==20474==
==20474== LEAK SUMMARY:
==20474== definitely lost: 0 bytes in 0 blocks
==20474== indirectly lost: 0 bytes in 0 blocks
==20474== possibly lost: 4,032 bytes in 7 blocks
==20474== still reachable: 32 bytes in 1 blocks
==20474== suppressed: 0 bytes in 0 blocks
==20474== Rerun with --leak-check=full to see details of leaked memory
==20474==
==20474== For counts of detected and suppressed errors, rerun with: -v
==20474== Use --track-origins=yes to see where uninitialised values come from
==20474== ERROR SUMMARY: 28 errors from 4 contexts (suppressed: 0 from 0)

上面的definitely lost代表必然的内存泄露,他包括indirectly【非直接】和directly【直接俩种, possibly lost是可能的内存泄露。我们只需要查看definitely lost即可

系统优化-内存-常用的内存泄露的排查方案

Posted on 2020-05-02 | Edited on 2022-09-21 | In 性能优化 , 内存

linux动态分配内存经常会遇到俩个问题:

  1. 内存没有及时回收,发生内存泄露
  2. 访问的是已分配边界外的内存,造成内存越界

这章我们来讲下内存泄露、以及排查内存泄露的方法

内存分配以及回收

我们的内存主要分为以下部分、堆、栈、只读端、数据段、内存映射段。

  • 栈:主要存放局部变量,由系统统一管理,所以不需要主动回收内存,一旦程序的作用域超过局部变量,就需要在堆中动态分配内存,在栈中的内存不会导致内存泄露
  • 堆:程序通过malloc申请内存,除非程序退出否则申请的内存不会释放。这部分内存会导致内存泄露
  • 只读段:保存程序的代码和常量,由于不需要继续申请内存,这部分会导致引起内存泄露
  • 数据段:保存静态变量和全局变量,由于申请时候已经知道了大小所以也不需要释放,这部分会导致引起内存泄露
  • 内存映射段:保存动态链接库和共享内存,由于共享内存的存在,

    综上,堆、和内存映射段会导致内存泄露

    内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。内存泄漏不断累积,甚至会耗尽系统内存。虽然oom会Kill程序,但是在kill程序前会有很多严重的问题

如何定位内存泄露

我们可以使用bcc-tools下的memleak,如图下面就是一个简单的内存泄露场景,child会分配内存而且没有回收

1
2
3
4
5
6
7
8
9
10
11
12
13
$ docker cp app:/app /app
$ /usr/share/bcc/tools/memleak -p $(pidof app) -a
Attaching to pid 12512, Ctrl+C to quit.
[03:00:41] Top 10 stacks with outstanding allocations:
addr = 7f8f70863220 size = 8192
addr = 7f8f70861210 size = 8192
addr = 7f8f7085b1e0 size = 8192
addr = 7f8f7085f200 size = 8192
addr = 7f8f7085d1f0 size = 8192
40960 bytes in 5 allocations from stack
fibonacci+0x1f [app]
child+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]

解决后的如下面所示

1
2
3
4
5
# 重新执行 memleak工具检查内存泄漏情况
$ /usr/share/bcc/tools/memleak -a -p $(pidof app)
Attaching to pid 18808, Ctrl+C to quit.
[10:23:18] Top 10 stacks with outstanding allocations:
[10:23:23] Top 10 stacks with outstanding allocations:

由mMemleak在centos中需要内核4.1以上支持,所以在老系统中我们用到valgrind的memcheck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 valgrind  --tool=memcheck --leak-check=summary ./xxx-api

==20474==
==20474== HEAP SUMMARY:
==20474== in use at exit: 4,064 bytes in 8 blocks
==20474== total heap usage: 16 allocs, 8 frees, 4,288 bytes allocated
==20474==
==20474== LEAK SUMMARY:
==20474== definitely lost: 0 bytes in 0 blocks
==20474== indirectly lost: 0 bytes in 0 blocks
==20474== possibly lost: 4,032 bytes in 7 blocks
==20474== still reachable: 32 bytes in 1 blocks
==20474== suppressed: 0 bytes in 0 blocks
==20474== Rerun with --leak-check=full to see details of leaked memory
==20474==
==20474== For counts of detected and suppressed errors, rerun with: -v
==20474== Use --track-origins=yes to see where uninitialised values come from
==20474== ERROR SUMMARY: 28 errors from 4 contexts (suppressed: 0 from 0)

上面的definitely lost代表必然的内存泄露,他包括indirectly【非直接】和directly【直接俩种, possibly lost是可能的内存泄露。我们只需要查看definitely lost即可

系统优化-内存-通过优化buffer和cache来优化程序

Posted on 2020-05-01 | Edited on 2022-09-21 | In 性能优化 , 内存
  • Buffer:是优化磁盘的读写
  • Cache:是优化文件的读写

    我们的程序可以通过优化Buffer和Cache来提高我们程序的运行效率。所谓工欲善其事必先利其器,我们下面介绍下如何去看我们内存的cache情况。

指标和工具

查看cache的命中情况我们主要会去查看cache的命中率。我们可以使用bcc-tools下的cachestat和cachetop

  • cachestat:统计整体内存的cache情况
  • cachetop:统计进程的内存cache情况
1
2
3
4
5
$ cachestat 1 3
TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
2 0 2 1 17 279
2 0 2 1 17 279
2 0 2 1 17 279
  • TOTAL:表示总的 I/O 次数
  • MISSES:缓存未命中
  • HITS:缓存命中
  • DIRTIES:脏页
  • BUFFERS_MB:buffer的数据
  • CACHED_MB:cache的数据
1
2
3
4
5

$ cachetop
11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
13029 root python 1 0 0 100.0% 0.0%

它的输出跟 top 类似,默认按照缓存的命中次数(HITS)排序,展示了每个进程的缓存命中情况。具体到每一个指标,这里的 HITS、MISSES 和 DIRTIES ,跟 cachestat 里的含义一样,分别代表间隔时间内的缓存命中次数、未命中次数以及新增到缓存中的脏页数。而 READ_HIT 和 WRITE_HIT ,分别表示读和写的缓存命中率。

还有一个缓存数据的大小的指标我们一般也会很关心,我们可以通过go语言写的pcstat查看

1
2
3
4
5
6
7

$ pcstat /bin/ls
+---------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|---------+----------------+------------+-----------+---------|
| /bin/ls | 133792 | 33 | 0 | 000.000 |
+---------+----------------+------------+-----------+---------+

这个就是/bin/ls的缓存情况

查找问题思路

我们通过cachetop找到问题的缓存命中率情况,有一种情况缓存命中率很高。但是缓存命中的数据很少【计算方式,HITS命中数*4k,因为缓存死以page为单位存的以page为4k】

系统优化-内存-内存的Buffer和Cache

Posted on 2020-04-28 | Edited on 2022-09-21 | In 性能优化 , 内存

我们用free去查看内存

1
2
3
              total        used        free      shared  buff/cache   available
Mem: 251 70 152 2 27 171
Swap: 135 17 118

我们可以看到Mem和Swap的场景。今天我们主要聊下buff和cache这俩个指标,它俩分别代表什么呢?他们来源于哪里呢

来源:/proc/meminfo,我们可以通过man free来看到下面的说明

1
2
3
4
5
6
7
buffers
Memory used by kernel buffers (Buffers in /proc/meminfo)

cache Memory used by the page cache and slabs (Cached and Slab in /proc/meminfo)

buff/cache
Sum of buffers and cache
  • buffers对应的/proc/meminfo的buffers值
  • cache对应的是/proc/meminfo的Cached值,和SReclaimable值

    它们分别是

  • Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。

  • Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
  • SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。

    buffer:主要针对磁盘操作,对磁盘的读、写都会影响buffer。
    cache:主要针对文件操作,对磁盘的读、写都会影响cache。

    注意:

  • 关于磁盘和文件的区别,磁盘是一个块设备,可以划分为不同的分区;

  • 在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件。
1…678…23

Liu hao

励志当好厨子的程序员

229 posts
54 categories
81 tags
RSS
GitHub E-Mail
© 2018 – 2023 Liu hao
Powered by Hexo v3.9.0
|
Theme – NexT.Pisces v7.0.0