14 | 容器中的内存与I/O:容器写文件的延时为什么波动很大?

本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。

Linux 中两种I/O模式:Direct I/O,Buffered I/O模式

Buffered I/O 是Linux的系统调用 write() 缺省模式。
这种模式的write()函数调用返回要快一些,使用的更普遍一些。

当使用Buffered I/O的应用程序从虚拟机迁移到容器后,多了Memory Cgroup的限制后,write()写相同大小的数据块花费的时间,波动延时比较大。

一、问题再现

场景:从文件中每次读取一个64KB大小的数据块写到新文件中,一共读写10GB的数据。同时做读取64KB数据块的时间花费记录。

分别在虚拟机和容器中运行,虚拟机的内存大于10GB,容器的Memory Cgroup内存限制1GB。

图里的纵轴是时间,单位 us;横轴是次数。
在这里插入图片描述
明显,容器中的波动要大于虚拟机的。

1.1、时间波动是因为Dirty Pages的影响吗?

使用Buffered I/O 时,用户的数据先写入到Page Cache里。
这些写入了数据的页面,在没有被写入磁盘前,被称为 dirty pages。

Linux 内核会有专门的内核线程(每个磁盘设备对应的kworker/flush线程)把dirty pages 写入到磁盘中。
那么我们自然猜测,Linux 内核对 dirty pages 的操作影响了Buffered I/O 的写操作。

/proc/sys/vm里和dirty page 相关的内核参数,决定了dirty page什么时候被写入到磁盘。

设定 dirty pages的内存 / 节点可用内存 * 100% = A

  • dirty_background_ratio:该数值是个百分比值,缺省10%,A 大于该值,内核flush 线程就会把dirty pages 刷到磁盘里。
  • dirty_background_bytes:和 dirty_background_ratio 作用相同,区别只是dirty_background_bytes是具体的字节数,它是用来定义dirty pages 内存的临界值,而不是比例值。

dirty_background_ratio 和 dirty_background_bytes 只有一个参数起作用,你给其中一个赋值后,另外一个参数就归零了。

  • dirty_ratio:缺省值是20%,如果A 大于此值,这时候正在执行Buffered I/O 写文件的进程会被阻塞住,直到它的数据页面写到磁盘为止。

  • dirty_bytes:此值与dirty_ratio对应,是具体的字节数。也是给其中一个赋值,另一个就会归零。

  • dirty_writeback_centisecs:这个是个时间值,百分之一秒为单位,缺省值是500,也就是5秒。 它表示每5秒钟会唤醒内核的flush线程来处理dirty pages。

  • dirty_expire_centisecs:这个也是一个时间值,以百分之一秒为单位,缺省值是3000,也就是30秒。它定义了dirty pages在内存中存放的最长时间,如果一个dirty page 超过这里的定义时间,那么内核的flush 线程会把这个页面写入磁盘。

猜测:
容器中进程写操作上的时间波动,可能是因为dirty_ratio 或者 dirty_bytes ,达到了限制值,进程被阻塞了。直到内核讲数据写到磁盘,写文件的进程才能继续运行,所以这一次的写文件的操作时间会增加。


在写文件的同时,查看节点上的dirty 相关的数值:

watch -n 1 "cat /proc/vmstat | grep dirty"

节点可用内存12GB(好像不是剩余可用内存的意思)
dirty_ratio 是 20%
dirty_background_ratio 是 10%
在 1GB memory 容器中写10GB 的数据,就会看到它实时 dirty pages 数目
也就是 /proc/vmstat 里的nr_dirty的数值(应该是页面数)
这个值对应的内存并不能达到 dirty_ratio 所占的内存值。
在这里插入图片描述

在 Linux 中,物理内存是以页为单位来管理的。 默认的,页的大小为 4KB。 1MB 的内存能划分为 256 页; 1GB 则等同于 256000 页。

或者就是把 dirty_ratio 、dirty_background_bytes 里写入一个很小的值:

echo 8192 > /proc/sys/vm/dirty_bytes
echo 4096 > /proc/sys/vm/dirty_background_bytes

然后再记录一下容器程序里每写入 64KB 数据块的时间,这时候,我们就会看到,时不时一次写入的时间就会达到 9ms,这已经远远高于我们之前看到的 200us 了。

因此,我们知道了这个时间的波动,并不是强制把 dirty page 写入到磁盘引起的。

(这个地方没看懂,是怎么排除掉这个关系的? 难道说是如果是dirty page写入导致的,延迟的时间应该是跟 200us 差不多?)

1.2、调试问题

下面使用perf 和 ftrace 工具,看下是哪个函数花费比较长的时间。

大致思路如下:容器中的进程用到了write()函数调用,写64KB数据库的时间增加了。

  • 找到内核中write()这个系统调用函数下,又调用了哪些子函数。可以查看代码,也可以使用perf工具。
  • 找到write()主要子函数后,可以用ftrace这个工具来trace 这些函数的执行时间,这样就可以找到花费时间最长的函数了。

第一步:容器启动写磁盘的进程后,在宿主机上得到这个进程的pid,然后运行下面的perf命令。

perf record -a -g -p <pid>

等磁盘的进程退出之后,这个perf record 也就停止了。

执行perf report 查看结果,展开 vfs_write() 函数就可以看到write() 系统调用下面调用了哪些主要的子函数:
在这里插入图片描述

第二步:把主要函数写入到 ftrace 的set_ftrace_filter 里,然后把ftrace 的tracer 设置为 function_graph,并且打开 tracing_on 开启追踪。

# cd /sys/kernel/debug/tracing
# echo vfs_write >> set_ftrace_filter
# echo xfs_file_write_iter >> set_ftrace_filter
# echo xfs_file_buffered_aio_write >> set_ftrace_filter
# echo iomap_file_buffered_write
# echo iomap_file_buffered_write >> set_ftrace_filter
# echo pagecache_get_page >> set_ftrace_filter
# echo try_to_free_mem_cgroup_pages >> set_ftrace_filter
# echo try_charge >> set_ftrace_filter
# echo mem_cgroup_try_charge >> set_ftrace_filter

# echo function_graph > current_tracer
# echo 1 > tracing_on

设置完成后,再运行一下容器中的写磁盘程序,同时从 ftrace 的 trace_pipe 中读取追踪到的这些函数。

这时可以看到,当需要申请Page Cache 页面的时候,write() 系统调用会反复地调用 mem_cgroup_try_charge(),并且在释放页面的时候,函数 do_try_to_free_pages() 花费的时间特别长,有50+us(时间单位,micro-seconds)这么多。

  1)               |  vfs_write() {
  1)               |    xfs_file_write_iter [xfs]() {
  1)               |      xfs_file_buffered_aio_write [xfs]() {
  1)               |        iomap_file_buffered_write() {
  1)               |          pagecache_get_page() {
  1)               |            mem_cgroup_try_charge() {
  1)   0.338 us    |              try_charge();
  1)   0.791 us    |            }
  1)   4.127 us    |          }1)               |          pagecache_get_page() {
  1)               |            mem_cgroup_try_charge() {
  1)               |              try_charge() {
  1)               |                try_to_free_mem_cgroup_pages() {
  1) + 52.798 us   |                  do_try_to_free_pages();
  1) + 53.958 us   |                }
  1) + 54.751 us   |              }
  1) + 55.188 us   |            }
  1) + 56.742 us   |          }1) ! 109.925 us  |        }
  1) ! 110.558 us  |      }
  1) ! 110.984 us  |    }
  1) ! 111.515 us  |  }

Linux 会把所有的空闲内存利用起来,一旦有Buffered I/O ,这些内存都会被用作 Page Cache。

当容器加了Memory Cgroup 限制了内存之后,对于容器中的 Buffered I/O ,就只能使用容器中允许使用的最大内存来做 Page Cache。

如果容器中的Cgroup 的memory.limit_in_bytes设置的比较小,而容器中的进程又有很大的I/O,这样申请新的Page Cache 内存的时候,又会不断释放老的内存页面,这些操作就会带来额外的系统开销。

二、重点总结

问题:
在容器中使用Buffered I/O 的方式写文件,时间的波动延迟会比较大。

过程:
Buffered I/O的方式,对于写入文件会先写到内存里,这样就产生了dirty pages。

一、先查看了Linux 对dirty pages的回收机制是否会影响到容器中写入数据的波动。

有几个关键参数:

  • dirty_background_ratio
  • dirty_ratio

上面两个是百分比值,是相对于节点 宿主机 可用内存的。

当dirty pages数量超过 dirty_background_ratio 对应内存量的时候,内核flush 线程就会开始把 dirty pages 写入磁盘;

当dirty pages数量超过dirty_ratio 对应的内存数量的时候,这是程序写文件的函数调用write()就会被阻塞住,直到这次调用的dirty pages全部写入到磁盘。

在节点是大内存容器,dirty_ratio 为缺省值20%,dirty_background_ratio 是缺省值10%时。 测试的时候同时观察 /proc/vmstat 中 nr_dirty 的实时数值可以发现。
此时 dirty pages 还不会触发阻塞进程的Buffered I/O 写文件操作。(因为阈值还比较大)

二、
使用perf 和ftrace 工具对容器中的写文件进程进行profile,我们用perf 得到了系统调用write()在内核中一系列子函数调用,再用ftrace 来查看这些子函数的调用时间。

根据ftrace 的结果,在测试的例子中:我们发现写数据到 Page Cache 的时候,需要不断的去释放原来的页面,这个时间开销是很大的。 造成容器中 Buffered I/O write()不稳定的原因,正是容器在限制内存后,Page Cache 的数量较小并且不断申请释放。

经验:对容器做Memory Cgroup 限制内存大小的时候,不仅要考虑容器中实际使用的内存量,还要考虑容器中程序 I/O 的量,合理预留足够的内存作为 Buffered I/O 的Page Cache。

例如 POD 内存是8G,jvm内存设置为4G,java的直接内存设置为1G,剩下空余的内存其他地方就可以用了,避免jvm全占了,出现其他的问题。

在需要反复写文件,例如写日志的情况下,需要考虑空闲出来部分内存。

还有一个思路:在程序中自己管理文件的cache 并且调用 Direct I/O 来读写文件,这样才会对应用程序的性能有一个更好的预期。

三、评论

1、
问题:dirty_background_ratio/dirty_background_bytes:
当dirty pages超过设置值时,系统才主动开始将脏页刷到磁盘。
dirty_ratio/dirty_bytes:
当dirty pages超过该设置值时,系统会将当前所有dirty pages 全部写入到磁盘,这个过程会阻塞write()调用。

请问老师:
关于dirty_background_ratio/dirty_background_bytes,在刷脏页到磁盘的过程中,是否也会阻塞当前的write()调用呢?还是由另一个后台线程执行刷盘的工作?是每隔一段时间刷一次吗?还是一直刷到dirty pages小于dirty_background_ratio/dirty_background_bytes了才停止?

课后思考题:
因为开启了"-direct=1",采用非 buffered I/O 文件读写的方式,所以过程中不会产生脏页,但是I/O的性能会下降。

回答:
如果dirty page的数目超过dirty_background_ratio/dirty_background_bytes对应的页面数,会有一个kernel thread把dirty page写入磁盘,这样不会阻塞当前的write()。这个kernel thread会一直刷到dirty pages小于dirty_background_ratio/dirty_background_bytes对应的页面才停止工作。

2、
问题:
12G,ratio 20%,1GB内存的这个case,我理解是,该case,即便是性能低,但是也没使用超过了dirty page的上限,为了说明dirty_ratio的设置不是性能低的原因

“然后再记录一下容器程序里每写入 64KB 数据块的时间,这时候,我们就会看到,时不时一次写入的时间就会达到 9ms,这已经远远高于我们之前看到的 200us 了。因此,我们知道了这个时间的波动,并不是强制把 dirty page 写入到磁盘引起的。”

对于这一段话不是很懂,和上面的12G,ratio 20% 的例子有何关系呢?

如果是控制变量法,感觉不太合理啊,dirty_page的使用量没超过上限的话,不影响,但是超过了设置的上限,就一定不影响吗?这个case是不是应该保持dirty_bytes的数量很大?

回答:
这个例子中后来是把dirty_bytes值设置小了,让它对write()操作产生了影响。只是这个影响产生的9ms等待时间要远远高于我们之前看到的200us。这样只是说明,200us的延时不是dirty_bytes的配置引起的。

echo 8192 > /proc/sys/vm/dirty_bytes
echo 4096 > /proc/sys/vm/dirty_background_bytes

我理解:如果问题再现中的情况也是因为达到了 dirty_bytes、dirty_background_bytes的限制了,那延迟时间就不会只有 200us,也会是类似 9ms 这种比较大延时。

3、
问题:
您好老师,想请教一下,perf能观察到哪个函数占用的cpu时间比较多,为什么还需要用ftrace来观察函数调用的时间呢; 另外ftrace统计的时间,是指cpu时间,还是墙上时间呢?

回答:
perf看到的函数占比是采样比例,有可能是函数调用的次数多。而ftrace可以看到单个函数的调用时间。
这里的时间值是wall-clock time

4、
问题:
文章中的工具分析是在宿主机还在容容内?

回答:
你指的是ftrace和perf? 在宿主机上运行的。

5、好问题
问题:
(1)节点可用内存是指这个节点的内存总量吗,还是剩余可分配量
(2)容器里的这个比值A,是等于 dirty pages 的内存 / 节点可用内存 *100%吗,还是说等于 dirty pages 的内存 / 容器可用内存 *100%。
(3)当节点上和容器里的/proc/sys/vm dirty page 相关内核参数配置了不同的值,会以哪个值为准呢

回答:

(1) 这里的可用内存可以理解为"free" 命令输出的"available" 内存。

(2) 是等于 dirty pages 的内存 / 节点可用内存 *100%

(3) /proc/sys/vm/dirty_*, 在容器和宿主机上是一样的,这个值没有namespace

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 静态时序分析是一种用于验证电子系统设计的方法,主要用于确定系统各个组件之间的时序关系和时钟限制。在静态时序分析,虚拟时钟和I/O延迟约束是两个重要的概念。 虚拟时钟是一种用于描述系统各个时钟域之间的关系的概念。在一个设计,可能存在多个时钟域,每个时钟域都有自己的时钟信号。这些时钟域之间可能存在数据传输和通信,而虚拟时钟可以用于描述这些时钟域之间的数据传输时序关系。通过定义虚拟时钟之间的约束,可以确保数据在不同的时钟域之间正确传输,保证整个系统的正常运行。 I/O延迟约束是指输入输出接口的传输延迟需要满足的要求。在一个电子系统,各种输入输出接口可能会存在不同的延迟。这些延迟会对系统的总体性能产生影响。静态时序分析可以帮助我们确定每个I/O接口的传输延迟,并通过设置延迟约束来保证系统能够在规定的时间内完成数据的传输以及响应。 综上所述,静态时序分析的虚拟时钟和I/O延迟约束是为了确保电子系统的正常运行和性能优化而引入的概念。虚拟时钟用于描述不同时钟域之间的数据传输时序关系,而I/O延迟约束则用于确保输入输出接口传输延迟能够满足系统的要求。通过静态时序分析,可以提前发现潜在的时序问题,并进行相应的优化和调整,从而提高电子系统的可靠性和性能。 ### 回答2: 静态时序分析是一种用于验证电路设计时序性能的方法,其包括虚拟时钟和I/O延迟约束两个关键概念。 虚拟时钟是一种用于解决时序约束和时序分析问题的技术。在电路设计,各个逻辑电路的时钟信号可能存在频率不一致、相位偏移等问题,需要将这些不同的时钟信号转化为一个相对统一的虚拟时钟进行分析。通过将不同的时钟信号都映射到虚拟时钟上,可以简化时序分析的复杂度,使得设计人员能够更好地评估电路的性能。虚拟时钟在设计过程的应用也能够帮助设计人员优化时序性能,提高电路运行的稳定性。 I/O延迟约束是指在电路设计对输入和输出端口的延迟进行限制,以确保系统能够满足时序要求。在实际应用,电路的输入输出时序要求非常重要,尤其是对处理高频信号的系统来说。通过设置I/O延迟约束,可以确保系统在特定时间内响应输入信号,并在规定的时间内输出正确的结果。设计人员可以根据I/O延迟约束的要求进行时序分析,确定逻辑电路的运行速度,以便满足系统的时序需求。 综上所述,虚拟时钟和I/O延迟约束是静态时序分析的关键概念。通过使用虚拟时钟将不同的时钟信号统一到一个时钟源上,可以简化时序分析的复杂度。而通过设置I/O延迟约束,可以确保电路在输入输出过程满足时序要求。这些方法对于电路设计的时序性能和稳定性非常重要,能够提高产品的质量和可靠性。 ### 回答3: 静态时序分析是一种用来分析数字电路的时序行为的方法。在静态时序分析过程,虚拟时钟和I/O延时约束是两个重要的概念。 虚拟时钟是一种用来描述数字电路各个部件之间时钟延迟关系的概念。在数字电路,时钟信号用来同步各个部件的操作,虚拟时钟可以根据时钟延迟来推导出各个部件之间的相对时间关系。通过虚拟时钟,可以检查电路在不同时钟周期下的性能以及时序约束是否能够被满足。例如,在设计,我们可以设置最大的时钟延迟,而根据虚拟时钟分析,若某些部件所需的时钟延迟超过了这个设定的最大值,就意味着设计不满足时序约束。 I/O延时约束是指在数字电路,与外部设备之间的输入输出时序关系。在实际应用,数字电路通常需要与外部设备进行数据通信,而这些设备通常有自己的时钟速度限制,因此需要根据外部设备的时钟信号来对输入输出操作进行时序约束。例如,在设计,如果某个输入信号需要在外部设备的时钟上升沿到达之前到达,就需要设置相应的I/O延时约束。静态时序分析可以通过分析虚拟时钟和I/O延时约束,来评估电路的性能和稳定性。 综上所述,虚拟时钟和I/O延时约束是静态时序分析的两个重要概念。虚拟时钟可以用来描述数字电路各个部件之间的时钟延迟关系,而I/O延时约束用来限制数字电路与外部设备之间的输入输出时序关系。通过对虚拟时钟和I/O延时约束的分析,可以评估电路在不同时钟周期下的性能和稳定性,并满足时序约束的要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值