前阵子调一个共享内存占用告警,平台反映某台机器 /dev/shm 快满了,可用内存掉得很快。第一反应是 ipcs 看一眼 System V 共享内存,结果什么都没有,再看监控图,内存一直挂在 tmpfs 上彻底下不来。后来查了一圈才发现,业务用的是 POSIX 共享内存,文件落在 /dev/shm 下面,只有从 /proc/PID/maps 里才能看到对应的映射关系。也就是从那天起,我开始认真琢磨“shmem 的映射监控按照绝对路径过滤”这件事。
说实话,这个需求看起来就是个过滤条件:监控共享内存的时候,按绝对路径把不关心的映射丢掉。但在实际落地的过程中,它牵扯到 /proc 文件系统解析、tmpfs 底层的 shmem 实现、容器命名空间、甚至文件被删除后的映射残留,坑一个接一个。这篇文章把我自己的完整思路和踩坑过程整理出来,给做运维监控、容器平台、数据库自查的同学一个可以直接抄作业的参考。
1. 先搞清楚监控对象:/dev/shm 里的共享内存到底长什么样
1.1 tmpfs、shmem 和 /dev/shm 的关系
很多人把 /dev/shm 当成普通目录,其实它是一个 tmpfs 文件系统,背后是内核的 shmem 模块。tmpfs 的核心特点:文件内容存在内存里,而不是磁盘上,读写速度接近内存,重启或者 umount 之后数据清空。Linux 发行版默认把它挂载在 /dev/shm,大小默认是物理内存的一半,可以通过 df -h /dev/shm 查看。正是因为“内容全部在内存”,tmpfs 在监控、限额方面几乎是裸奔的:普通磁盘有 quota,tmpfs 虽然也可以挂载时指定 inode/block 上限,但现实中很多业务没有人去设置它。
在共享内存的体系里,/dev/shm 又承担着 POSIX 共享内存落地路径的角色。glibc 的 shm_open 实现本质上是在 /dev/shm 下创建文件,shm_unlink 是删除对应文件,一个进程打开这个文件、mmap 成 MAP_SHARED,其他进程通过同一个路径再次打开映射到同一块物理内存,这样就实现了跨进程通信。所以你会看到,凡是用了 shm_open 或直接 open("/dev/shm/xxx") 再 mmap 的进程,在 /proc/PID/maps 里的映射路径就是 /dev/shm/xxx,这个“路径”就是我们做按绝对路径过滤的根本依据。
1.2 不按路径过滤,共享内存监控会失控
假设一台主机上跑了三个业务模块:order_service、pay_service、report_service,每个模块都往 /dev/shm 里写自己的缓存文件。如果监控只做总量统计,一旦 /dev/shm 占用超过了 80% 触发告警,事情就来了:三个模块哪个是罪魁祸首?进到机器上盲目去看,可能 order_service 只是被动缓存了 GB 级别小文件,report_service 才是罪魁祸首,但总量指标看不出来。更麻烦的是,如果监控脚本只是把 /proc/*/smaps 全部解析一遍然后求和,不区分路径,那么 total_shmem_usage 这个指标根本没法作为自动扩缩容、自动清理策略的输入。
有了按绝对路径过滤以后,事情就变成了:order_service 的前缀匹配到多少,pay_service 匹配到多少,report_service 匹配到多少。每一类业务的共享内存占用是独立的,告警可以精确到业务目录级别,清理动作也可以只针对超过阈值的那个目录。对于需要细粒度定位的场景,比如某个服务在 /dev/shm 下建立的共享缓存、消息中间件的 mmap 索引文件、数据分析引擎的跨进程结果集,这个维度尤其重要,因为它们的共享内存文件往往有明确的路径前缀,最容易实现自动化巡检。
1.3 绝对路径过滤的本质:把路径当业务命名空间
我要特意强调一个观点:绝对路径过滤不只是一个字符串匹配的工
