最近在做任务的压测操作,我们有几个任务由于打印日志的代码问题,导致频繁的打日志,由于我们的任务运行在Docker容器中,所以使用docker stats进行容器内存的监控统计操作。
具体的操作流程如下:
1.首先使用docker stats进行内存和cpu使用的监控
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
e39d5e04cac6 br-flink-taskmanager 31.80% 7.568GiB / 31.25GiB 24.22% 0B / 0B 0B / 320MB 682
可以看到内存占用是7.5G左右,但是我们任务的整个内存限制为6G,这里超了一个多G,在其他的时候监控,还会发现这里的内存占用到了20G的情况。这里我们还怀疑使用的logback出现了内存泄漏的情况。。。。。
2.查看docker容器的内存监控信息:cat /sys/fs/cgroup/memory/docker/{容器id}/memory.stat
cache 2599772160
rss 5964361728
rss_huge 0
mapped_file 339968
swap 0
pgpgin 8397732
pgpgout 6306879
pgfault 2801823
pgmajfault 0
inactive_anon 0
active_anon 5964361728
inactive_file 443101184
active_file 2156646400
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 2599772160
total_rss 5964361728
total_rss_huge 0
total_mapped_file 339968
total_swap 0
total_pgpgin 0
total_pgpgout 0
total_pgfault 0
total_pgmajfault 0
total_inactive_anon 0
total_active_anon 5964361728
total_inactive_file 443101184
total_active_file 2156646400
total_unevictable 0
以上是docker容器中监控看到的,发现cache和rss相关的参数,可以看到进程实际使用的物理内存rss约为5.9G,而cache占用了约2.5G内存,总的加起来约等于7G多,所以会不会docker stats统计的是rss + cache的和。
memory.stat的参数解释
统计 | 描述 |
---|---|
cache | 页缓存,包括 tmpfs(shmem),单位为字节 |
rss | 匿名和 swap 缓存,不包括 tmpfs(shmem),单位为字节 |
mapped_file | memory-mapped 映射的文件大小,包括 tmpfs(shmem),单位为字节 |
pgpgin | 存入内存中的页数 |
pgpgout | 从内存中读出的页数 |
swap | swap 用量,单位为字节 |
active_anon | 在活跃的最近最少使用(least-recently-used,LRU)列表中的匿名和 swap 缓存,包括 tmpfs(shmem),单位为字节 |
inactive_anon | 不活跃的 LRU 列表中的匿名和 swap 缓存,包括 tmpfs(shmem),单位为字节 |
active_file | 活跃 LRU 列表中的 file-backed 内存,以字节为单位 |
inactive_file | 不活跃 LRU 列表中的 file-backed 内存,以字节为单位 |
unevictable | 无法再生的内存,以字节为单位 |
hierarchical_memory_limit | 包含 memory cgroup 的层级的内存限制,单位为字节 |
hierarchical_memsw_limit | 包含 memory cgroup 的层级的内存加 swap 限制,单位为字节 |
3.通过一番搜索,找到了可以通过/proc/sys/vm/drop_caches文件来进行缓存的清除操作
echo 1 > /proc/sys/vm/drop_caches # 仅清除页面缓存
echo 2 > /proc/sys/vm/drop_caches # 清除目录项和inode
echo 3 > /proc/sys/vm/drop_caches # 清除页面缓存、目录项以及inode
我先执行:echo 1 > /proc/sys/vm/drop_caches,然后再docker stats观察内存占用
[root@br-apm-009 ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
e39d5e04cac6 br-flink-taskmanager 0.68% 7.624GiB / 31.25GiB 24.40% 0B / 0B 0B / 320MB 681
[root@br-apm-009 ~]# echo 1 > /proc/sys/vm/drop_caches
[root@br-apm-009 ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
e39d5e04cac6 br-flink-taskmanager 0.69% 5.534GiB / 31.25GiB 17.71% 0B / 0B 8.19kB / 320MB 682
如上可以看到清理缓存内存掉到了5.5G左右了,所以docker stats统计的时候会加上cache/buffer相关的内存。
4.为了验证进程的实际内存是不是真的占用这么多,直接去观察/proc/pid/status中是否真的是占用了5.5G多。
[root@br-apm-009 ~]# ps -ef | grep taskmanager
bonree 35098 35077 99 11:03 ? 05:43:35 /usr/lib/jvm/java-11-openjdk-amd64/bin/java -Xmx4429185024 -Xms4429185024 -XX:MaxDirectMemorySize=671088640 -XX:MaxMetaspaceSize=268435456 -XX:+IgnoreUnrecognizedVMOptions -Dlog.file=/opt/flink/log/flink--taskexecutor-77-br-apm-009.log -Dlog4j.configuration=file:/opt/flink/conf/log4j-console.properties -Dlog4j.configurationFile=file:/opt/flink/conf/log4j-console.properties -Dlogback.configurationFile=file:/opt/flink/conf/logback-console.xml -classpath /opt/flink/lib/flink-cep-1.19.1.jar:/opt/flink/lib/flink-connector-files-1.19.1.jar:/opt/flink/lib/flink-csv-1.19.1.jar:/opt/flink/lib/flink-json-1.19.1.jar:/opt/flink/lib/flink-scala_2.12-1.19.1.jar:/opt/flink/lib/flink-table-api-java-uber-1.19.1.jar:/opt/flink/lib/flink-table-planner-loader-1.19.1.jar:/opt/flink/lib/flink-table-runtime-1.19.1.jar:/opt/flink/lib/ingester.jar:/opt/flink/lib/flink-dist-1.19.1.jar:::: org.apache.flink.runtime.taskexecutor.TaskManagerRunner --configDir /opt/flink/conf -D taskmanager.memory.network.min=536870912b -D taskmanager.cpu.cores=25.0 -D taskmanager.memory.task.off-heap.size=0b -D taskmanager.memory.jvm-metaspace.size=268435456b -D external-resources=none -D taskmanager.memory.jvm-overhead.min=536870912b -D taskmanager.memory.framework.off-heap.size=134217728b -D taskmanager.memory.network.max=536870912b -D taskmanager.memory.framework.heap.size=134217728b -D taskmanager.memory.managed.size=536870912b -D taskmanager.memory.task.heap.size=4294967296b -D taskmanager.numberOfTaskSlots=25 -D taskmanager.memory.jvm-overhead.max=536870912b -D blob.server.port=6430 -D query.server.port=6431
[root@br-apm-009 ~]# cat /proc/35098/status
Name: java
Umask: 0022
State: S (sleeping)
Tgid: 35098
Ngid: 0
Pid: 35098
PPid: 35077
TracerPid: 0
Uid: 2048 2048 2048 2048
Gid: 2048 2048 2048 2048
FDSize: 1024
Groups: 2048
VmPeak: 8330360 kB
VmSize: 8330360 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 5807356 kB
VmRSS: 5769756 kB
RssAnon: 5742464 kB
RssFile: 27292 kB
RssShmem: 0 kB
VmData: 8140832 kB
VmStk: 140 kB
VmExe: 8 kB
VmLib: 18112 kB
VmPTE: 15028 kB
VmSwap: 0 kB
Threads: 683
SigQ: 0/127928
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 2000000101005ccf
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000
NoNewPrivs: 0
Seccomp: 2
Speculation_Store_Bypass: thread force mitigated
Cpus_allowed: 00000000,0000ffff
Cpus_allowed_list: 0-15
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 192
nonvoluntary_ctxt_switches: 7
其中/proc/pid/status中相关参数的解释如下:
Name: 进程的名称,例如"java"或"bash"。
State: 进程的状态,例如"running"或"sleeping"。
Tgid: 进程组ID,即进程的ID号。
Pid: 进程的ID号。
PPid: 父进程的ID号。
TracerPid: 跟踪进程的ID号。
Uid: 进程的用户ID号。
Gid: 进程的组ID号。
FDSize: 进程的文件描述符数。
Groups: 进程所属的组ID号列表。
VmPeak: 进程的虚拟内存峰值,即进程使用的最大内存大小。
VmSize: 进程的虚拟内存大小,即进程实际使用的内存大小。
VmLck: 进程的虚拟内存锁定大小,即进程被锁定的内存大小。
VmHWM: 进程的虚拟内存高水位线,即进程使用的最大内存大小。
VmRSS: 进程的实际内存大小,即进程在物理内存中的大小。
RssAnon: 进程的非映射内存大小,即进程的匿名内存大小。
RssFile: 进程的映射文件大小,即进程的文件映射内存大小。
RssShmem: 进程的共享内存大小,即进程的共享内存大小。
VmData: 进程的数据段内存大小,即进程使用的数据段内存大小。
VmStk: 进程的堆栈内存大小,即进程使用的堆栈内存大小。
VmExe: 进程的可执行文件大小,即进程使用的可执行文件大小。
VmLib: 进程的库文件大小,即进程使用的库文件大小。
VmPTE: 进程的页表项大小,即进程使用的页表项大小。
Threads: 进程的线程数。
SigQ: 进程的信号队列大小。
SigPnd: 进程的等待信号列表。
ShdPnd: 进程的等待共享内存列表。
SigBlk: 进程的阻塞信号列表。
SigIgn: 进程的忽略信号列表。
SigCgt: 进程的当前信号掩码。
CapInh: 进程的继承能力。
CapPrm: 进程的 permitted 能力。
CapEff: 进程的有效能力。
Cpus_allowed:进程可以使用的CPU列表。
Mems_allowed:进程可以使用的内存列表。
Voluntary_ctxt_switches:进程主动进行的上下文切换次数。
Nonvoluntary_ctxt_switches:进程被动进行的上下文切换次数。
如上可以看到vmRss是进程实际占用的内存大小,大小约为5.7G,和我们上面分析的实际内存占用是符合的。
5.最后还有一个问题为什么进程会占用额外的cache/buffer部分内存呢?
Buffer 的具体职责
- 在当前的系统实现里,buffer主要是设计用来在系统对块设备进行读写时作为缓存来使用。这意味着对块的操作会使用buffer进行缓存,比如我们在格式化文件系统的时候。
- 但是一般情况下两个缓存系统是一起配合使用的,比如当我们对一个文件进行写操作的时候,cache的内容会被改变,而buffer则用来将cache的page标记为不同的缓冲区,并记录是哪一个缓冲区被修改了。
- 这样,内核在后续执行脏数据的回写(writeback)时,就不用将整个page写回,而只需要写回修改的部分即可。
Cache 的具体职责
- cache主要用来作为文件系统上的文件数据的缓存来用,当进程对文件有read/write操作的时候。包括将文件映射到内存的系统调用mmap,就会用到cache。
- 因为cache被作为文件类型的缓存来用,所以事实上也负责了大部分的块设备文件的缓存工作。
cache/buffer部分的内存在系统内存吃紧的时候会释放出来的。
参考文章:
https://blog.csdn.net/xs_chang/article/details/132602282
https://blog.csdn.net/jiangbb8686/article/details/102539805
https://blog.csdn.net/songguangfan/article/details/121475879