简介:面向银河麒麟V10服务器运维人员的内存释放定时处置方案,聚焦系统长时间运行后可用内存持续下降、疑似内存泄漏的排查与缓解场景,适合具备一定Linux基础的运维人员与系统管理员参考。压缩包共3个文件,以2个Shell脚本和1份txt配置说明为主,脚本承担定时清理与任务调度,说明文档交代部署方式与参数调整,整体仅2KB,轻量易取用。已有785人学习下载,可作为中小规模服务器日常巡检的辅助手段。内容围绕内存监控、泄漏定位、日志分析、源码审查、补丁更新、系统参数优化及服务重启等处置思路展开,配套脚本可将释放动作固化为计划任务,减少人工干预;读者可据此建立从发现到缓解的处理链路,并结合自身业务调整执行频率与触发条件,在保障高可用前提下降低内存不足带来的性能波动。
1. 银河麒麟V10内存不释放:先分清「缓存占着」和「进程漏了」
机房里有台银河麒麟V10的机器,跑着两套 Java 业务和一套中间件,白天free -h里的 available 一路往下走,到晚上只剩两三百兆,业务却一点不卡。值班的运维同学第一反应通常是加个定时任务把缓存刷掉,脚本上线三天,内存曲线确实好看了,磁盘 IO 却开始抖。这份资源讲的就是这件事:在麒麟V10上做定时内存回收,怎么做到既压住曲线又不伤业务,出了问题又该怎么回头查。
它适合三类人:被「内存不释放」告警追着跑的运维、要给甲方交一份可落地巡检方案的实施工程师、以及刚接手国产化替代机器、还不清楚 4.19 内核这套内存账怎么算的人。后面按「先看懂账本、再写定时任务、最后调参数和排坑」的顺序拆,中间给的都是能直接抄的脚本和 unit 文件。
2. 把内存账本摊开:MemAvailable、slab、匿名内存各算各的
在麒麟V10上,「内存不释放」这句话本身就有歧义。同一个现象,可能是 page cache 攒多了、可能是 dentry 攒多了、也可能是某个进程真的在漏。三种根因对应三种完全不同的处置手段,用错手段的后果是把 IO 打满还没解决问题。所以动手写定时任务之前,先把/proc/meminfo这本账摊开看一遍,比什么都值。
2.1 MemAvailable 才是判据,不是 free 那一列
先明确一个前提:Linux 把空闲内存拿去做页缓存,这是设计,不是缺陷。free -h里free那一列小得可怜,buff/cache那一列很大,属于正常状态,内核在没人用内存的时候不用白不用,等进程要内存了会自动把干净的 page cache 让出来。真正该盯的是available,它是内核给出的估算值,含义是「在不触发换页的前提下,还能分给新进程用多少」。判断一台麒麟V10是不是真的内存吃紧,看这个数的占比,别被free那一列吓到。
不同 SP 版本的麒麟V10内核小版本略有差异,先确认一下自己手上这台机器的底细。4.19 系列的回收行为和 5.10 有明显区别,尤其是 multigenerational LRU 之类的东西在 4.19 上根本没有,别拿别的发行版的经验硬套。
# 确认内核与发行版本,不同 SP 版本的内存回收行为有细微差异 uname -r cat /etc/kylin-build # 部分版本有,没有就跳过 # 看总体水位,重点看 available 占比和 swap 使用 free -m # 精确读取关键字段,单位 KB,除以 1024 转成 MB 便于阅读 awk '/^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree):/ \ {printf "%-14s %8.0f MB\n", $1, $2/1024}' /proc/meminfoawk里$1是字段名(带冒号),$2是数值,/^(...):/用括号做正则分组,把六个字段一次捞出来。这条命令的好处是输出稳定,可以直接塞进巡检脚本做趋势采集,不会像free那样因为版本不同列名变化而导致解析失败。如果MemAvailable长期在总内存的 15% 以下、并且SwapFree在持续下降,那才是真的需要处理的信号;如果只是Cached大而available还有 40%,动它纯属给自己找事。
2.2 把「不释放」拆成三类:page cache、slab、匿名内存
drop_caches这三个数字不是万能的,它只对「可回收的内核缓存」有效。搞清楚哪些字段对应哪类占用,就知道什么时候该刷、什么时候刷了也白刷。下面这张表是我自己排查时贴在工位上的对照表,字段名直接对应/proc/meminfo。
| 占用类型 | meminfo 字段 | drop_caches 是否管用 | 典型来源 |
|---|---|---|---|
| 页缓存 | Cached、Buffers | 管用,写 1 或 3 | 文件读写、数据库数据文件、日志写入 |
| dentry/inode | SReclaimable | 管用,写 2 或 3 | 遍历大量小文件、目录扫描、打包解包 |
| 不可回收 slab | SUnreclaim | 不管用 | 内核对象泄漏、驱动、部分文件系统元数据 |
| 匿名内存 | AnonPages | 不管用 | Java 堆、进程 malloc、真实内存泄漏 |
| 共享内存/tmpfs | Shmem | 不管用 | /dev/shm、tmpfs 挂载点、容器共享卷 |
| 大页 | HugePages_Total/Free | 不管用 | 数据库预分配、DPDK |
看表能得出一个很实用的结论:Cached和SReclaimable加起来占了大部分,刷缓存才有意义;AnonPages和Shmem占大头,刷一百遍也不会掉一兆。尤其Shmem这个字段最容易被忽略,有些服务把临时文件往/dev/shm里写,文件删了但进程还拿着句柄,df -h /dev/shm看着是满的,free看着内存没释放,两边都对得上,但和 page cache 一点关系没有。
SUnreclaim是另一个需要单独盯的字段。它正常应该是几百兆的量级,如果发现它随时间单调上涨、且slabtop里某个kmalloc-*或dentry缓存条目数一路狂飙,那基本可以判定是内核态的问题,定时刷缓存只会掩盖症状,真正的后悔药是升级内核或对应驱动,这个后面第 5 章会展开。
2.3 现场取证:五条命令定位是谁在吃内存
光看总量没用,得落到具体进程和具体缓存类型。下面这五条命令是我到现场必跑的一套,从粗到细,五分钟内能给出一个初步结论。跑之前确认sysstat和slabtop都在,麒麟V10 一般预装了procps-ng和sysstat,缺的话用dnf install sysstat补上。
# 1) 总体水位与结构,先判断是可回收缓存还是匿名内存 free -m grep -E '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|AnonPages|Shmem|Slab|SReclaimable|SUnreclaim|HugePages_Total):' /proc/meminfo # 2) 按 RSS 排前 20 个进程,RSS 高且持续上涨的才是泄漏嫌疑 ps -eo pid,ppid,rss,vsz,%mem,comm --sort=-rss | head -20 # 3) slab 大户,看哪一类内核对象在涨 slabtop -o -s c | head -20 # 4) 缺页与换页速率,si/so 长期非 0 说明已经在啃 swap vmstat 1 5 # 5) 近 24 小时趋势,区分缓步爬升和台阶式跳变 sar -r -f /var/log/sa/sa$(date +%d) | tail -30逐条说参数。ps的--sort=-rss表示按 RSS 降序,负号是降序标记;rss是常驻物理内存,vsz是虚拟地址空间,Java 进程的vsz通常大得离谱但没参考价值,看rss才有意义。slabtop -o -s c里-o是只输出一次不进入交互,-s c是按缓存大小排序,把最大的几类排前面;如果你更关心对象数量而不是字节数,把c换成a。
vmstat 1 5每秒钟采样一次共五次,重点看si(从 swap 换入)和so(换出到 swap)两列,长期非 0 说明物理内存已经不够用了,这时候内存「不释放」不是缓存问题而是容量问题。sar -r读的是当天的心跳文件,麒麟V10 上sysstat默认可能没启用采集,第 5 章有一条专门讲这个坑。
ps那一步有个细节:单个进程的rss之和经常远大于实际物理内存,因为共享库被重复计算。想更准一点可以装smem,它按 PSS 统计,能把共享页按比例分摊,代价是要多装一个包、多跑几秒。我一般先用ps粗筛,锁定可疑进程之后再用smem -t -p | tail -5复核,避免冤枉一个其实只占正常堆内存的 Java 进程。
3. 定时回收的三种落地方式:阈值脚本 + systemd timer 优先
定时回收这件事,做对比不做更重要。裸写一个*/30 * * * * sync && echo 3 > /proc/sys/vm/drop_caches是最常见的翻车起点:没有阈值判断,内存富余的时候也照刷不误;没有防重入,两分钟一次的定时任务和手动执行撞在一起;没有日志,出了 IO 抖动找不到时间点对账。下面这套方案把阈值、锁、日志、随机延迟都补齐,脚本本体两种调度方式都能用。
3.1 为什么优先 systemd timer 而不是 cron
cron 不是不能用,它简单、兼容性最好、老运维一眼能看懂,但它在麒麟V10上有几个绕不开的短板。第一是日志,cron 的输出去向要靠MAILTO或重定向,机器上没配邮件就相当于扔进黑洞,出问题只能靠猜;systemd timer 触发的服务,标准输出和logger写的日志都进 journal,journalctl -t mem-reclaim一条命令就能把时间线拉齐。
第二是并发与随机性。多台机器同一个整点刷缓存,存储侧会看到一波全闪的读 IO 尖峰,RandomizedDelaySec能把触发时间打散,避免这种自己制造的雪崩。第三是可观测性,systemctl list-timers能直接看到下次触发时间和上次执行结果,Persistent=true还能在机器重启后补跑一次错过的任务。
麒麟V10 的 systemd 版本是 243,OnCalendar语法完整支持,Timer单元的这些参数都能用,不必担心兼容性。如果你的环境里还有大量 CentOS 7 遗产机器需要统一管理,那 cron 更省事,但脚本里的阈值判断和flock一定要保留,别为了省事把护栏拆了。
3.2 回收脚本:带阈值、锁、日志的完整实现
脚本放/usr/local/sbin/mem-reclaim.sh,权限0755,属主root:root。设计上只做三件事:判断可用内存是否低于阈值、低于才动手、动完记录回收前后的对比。所有可调参数走环境变量,方便在 unit 文件里覆盖而不改脚本。
#!/bin/bash # /usr/local/sbin/mem-reclaim.sh # 作用:MemAvailable 低于阈值时回收 page cache 与 dentry/inode,并记录前后水位 set -uo pipefail THRESHOLD_PCT="${THRESHOLD_PCT:-15}" # 可用内存百分比下限,低于它才回收 LOCK_FILE="/var/lock/mem-reclaim.lock" # 防重入锁文件 LOG_TAG="mem-reclaim" log() { logger -t "$LOG_TAG" -p "$1" "$2"; } # 从 /proc/meminfo 精确取某个字段的 KB 值,字段名带冒号 read_meminfo() { awk -v k="$1" '$1 == k":" {print $2}' /proc/meminfo } TOTAL_KB=$(read_meminfo MemTotal) AVAIL_KB=$(read_meminfo MemAvailable) AVAIL_PCT=$(( AVAIL_KB * 100 / TOTAL_KB )) # 防重入:同一时刻只允许一个实例在跑,拿不到锁直接退出 exec 9>"$LOCK_FILE" if ! flock -n 9; then log user.warning "上一次回收尚未结束,本次跳过" exit 0 fi # 阈值判断:内存富余时什么都不做,这是和裸 drop_caches 最大的区别 if [ "$AVAIL_PCT" -ge "$THRESHOLD_PCT" ]; then log user.info "可用 ${AVAIL_PCT}% >= ${THRESHOLD_PCT}%,跳过回收" exit 0 fi log user.warning "可用 ${AVAIL_PCT}% 低于阈值,开始回收" sync # 脏页先落盘,否则回收瞬间会顶出一波写 IO echo 3 > /proc/sys/vm/drop_caches # 3 = page cache + dentry + inode sleep 2 # 给内核一点时间完成回收 AVAIL_KB=$(read_meminfo MemAvailable) # 重新读取,别用回收前的旧值 log user.info "回收完成,可用 $(( AVAIL_KB / 1024 )) MB($(( AVAIL_KB * 100 / TOTAL_KB ))%)"几个地方值得展开。set -uo pipefail没有加-e,是因为脚本里flock失败、awk取不到值这些分支需要自己处理退出码,开了-e反而容易在预期内的分支上直接中止。exec 9>"$LOCK_FILE"把文件描述符 9 绑到锁文件上,flock -n 9拿不到锁立即返回非 0,比mkdir做锁干净得多,进程退出时锁自动释放,不会留下需要人工清理的残留。
echo 3会同时清 page cache 和 dentry/inode;如果只想清页缓存用echo 1,只想清目录项用echo 2。生产上我一般先上1,观察一周没副作用再考虑加2,因为 dentry 清掉之后,业务侧大量小文件的路径查找会立刻变慢,ls一个几万文件的目录能从毫秒级掉到秒级。sync那一步不能省,麒麟V10 默认dirty_ratio是 20%,如果回收时还有几百兆脏页没落盘,drop_caches会连带触发回写,IO 尖峰就是这么来的。
logger写的是 syslog,麒麟V10 上默认进/var/log/messages,同时也被 journald 收走,两处都能查。log user.warning的优先级用warning是为了在journalctl -p warning过滤时能一眼看到异常回收记录,正常跳过用info,不污染告警视图。
3.3 service 与 timer 的写法、启用与验证
unit 文件放/etc/systemd/system/,service 用Type=oneshot,因为脚本执行完就退出,不需要常驻。加Nice和IOSchedulingClass=idle是让回收过程本身别和业务抢 CPU 和 IO,MemoryMax限制这个一次性任务自己的内存开销,防止脚本本身出问题。
# /etc/systemd/system/mem-reclaim.service [Unit] Description=Reclaim page cache when MemAvailable is below threshold After=multi-user.target [Service] Type=oneshot Environment=THRESHOLD_PCT=15 ExecStart=/usr/local/sbin/mem-reclaim.sh Nice=10 IOSchedulingClass=idle MemoryMax=64M PrivateTmp=true# /etc/systemd/system/mem-reclaim.timer [Unit] Description=Run mem-reclaim every 30 minutes [Timer] OnBootSec=10min OnCalendar=*:0/30 AccuracySec=1min RandomizedDelaySec=120 Persistent=true Unit=mem-reclaim.service [Install] WantedBy=timers.targetOnBootSec=10min让机器起来十分钟后才第一次执行,避开开机阶段大量服务抢内存的窗口。OnCalendar=*:0/30表示每半小时的整点,AccuracySec=1min允许内核在这一分钟内合并唤醒以省电,RandomizedDelaySec=120在触发时间上再加最多两分钟的随机偏移,多台机器同时部署时靠它把 IO 打散。Persistent=true记录上次触发时间,机器关机期间错过的任务在开机后补一次,不会因为重启就漏掉一整段。
启用和验证的命令如下,list-timers那步一定要看,能确认下次触发时间是不是符合预期,别配完了以为在跑其实 Unit 名字写错了。
# 赋予执行权限并重载 unit chmod 0755 /usr/local/sbin/mem-reclaim.sh systemctl daemon-reload # 启用并立即启动定时器 systemctl enable --now mem-reclaim.timer # 确认下次触发时间与上次执行结果 systemctl list-timers mem-reclaim.timer --all # 手动跑一次服务,验证脚本本身没问题 systemctl start mem-reclaim.service # 两处日志都要看:logger 写的和 unit 自身的 journalctl -t mem-reclaim -n 20 --no-pager journalctl -u mem-reclaim.service -n 20 --no-pager手动执行那次很关键。systemctl start会走完整的 unit 环境,包括Environment=THRESHOLD_PCT=15,能把脚本里环境变量读取、路径、权限的问题一次性暴露出来。我习惯在这步临时把阈值调到 99,强制它走一次回收分支,确认logger和 journal 里都有完整的前后对比记录,再把阈值改回 15。
3.4 cron 方案的写法与三个必须加的护栏
老环境或者批量管理工具只认 crontab 的时候,脚本原样复用,只是调度换成 cron。放/etc/cron.d/mem-reclaim,注意这个目录下的文件格式比用户 crontab 多一个用户字段,写错了会被 cron 静默忽略。
# /etc/cron.d/mem-reclaim SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin */30 * * * * root /usr/local/sbin/mem-reclaim.sh >/dev/null 2>&1三个护栏:第一,PATH必须显式声明,cron 的环境极简,脚本里但凡调用了不在/usr/bin下的命令就会莫名其妙失败;第二,脚本内的flock保留,cron 本身不管并发,两次任务重叠时全靠这把锁;第三,输出重定向到/dev/null是安全的,因为关键信息已经由logger送进 syslog,但如果你的脚本里有echo调试语句,记得改成logger,否则这些信息会丢。
cron 的排查入口和 systemd 完全不同,看/var/log/cron有没有执行记录,看/var/log/messages里CROND的报错。麒麟V10 上如果开了安全加固,cron 目录下新建的文件可能带错 SELinux 上下文导致拒绝执行,ls -Z /etc/cron.d/mem-reclaim看一眼,不对就用restorecon -v修。
4. 内核参数的四组旋钮:drop_caches 之外还能调什么
定时回收是被动手段,它只能在内存已经紧张之后救场。真正让曲线平缓的是几个内核参数,它们决定了内核在什么水位开始主动回收、回收时优先动谁。这组参数调好了,定时任务的触发频率能降一个数量级;调坏了,表现是业务长尾延迟变高但内存曲线很漂亮,属于典型的指标好看体验差。
4.1 vm.min_free_kbytes:水位线别乱拉
这个参数是每个内存 zone 的低水位基准,内核低于这个水位就开始直接回收,直接回收是同步的,会阻塞正在申请内存的进程,表现出来就是业务偶发卡顿。默认值由内核按内存大小自动计算,通常只有几十兆,在突发大块分配的场合偏小。
设大一点能让内核提前进入后台回收,避免业务线程撞上直接回收,但代价是空闲内存被提前「占住」,page cache 能用的空间变少,缓存命中率下降,IO 变多。经验值是按物理内存的 2%~3% 取,64G 的机器取 1G 左右,但别超过 4G,再大就纯粹是浪费。麒麟V10 上改完用sysctl -n vm.min_free_kbytes确认生效,单位是 KB。
4.2 vm.vfs_cache_pressure:控制 dentry 的回收积极性
默认 100,表示 dentry 和 inode 缓存与 page cache 按同等比例回收。大于 100 更积极回收 dentry/inode,适合小文件遍历特别多的场景,比如日志采集、大量临时文件读写;小于 100 更倾向保留,适合目录查找频繁的服务。
这个值调到 0 是危险的,内核几乎不再回收 dentry,内存会被目录项慢慢吃满,而且drop_caches之外的手段都没辙。生产上我一般取 150,小文件密集的机器取到 200,效果是SReclaimable的爬升速度明显放缓,定时刷缓存的必要性下降。
4.3 vm.swappiness 与脏页比例:延迟和 IO 的平衡
swappiness控制匿名页换出的倾向。麒麟V10 默认值是 60,对内存型业务偏激进,容易在内存还有余量的时候就把热点匿名页换出去,等业务再访问时又要换回来,来回折腾出长尾延迟。调到 10 能显著减少这种无谓换页,代价是真到内存不足时回收会更晚触发,需要配合min_free_kbytes一起看。
脏页比例是另一个影响drop_caches体验的参数。dirty_ratio是单进程能产生的脏页上限,dirty_background_ratio是后台回写线程启动的门槛。默认 20/10 意味着最多能攒下 20% 内存的脏页,回收前那一波集中回写足以把机械盘打满。调到 20/5 让回写更平滑,代价是写吞吐的峰值会低一些。
# /etc/sysctl.d/90-mem-reclaim.conf # 单位均为 KB;min_free_kbytes 按物理内存 2%~3% 估,64G 机器取 1G vm.min_free_kbytes = 1048576 # 大于 100 更积极回收 dentry/inode,小文件多的机器可以上到 150~200 vm.vfs_cache_pressure = 150 # 降低匿名页换出倾向,减少回收后业务的长尾延迟 vm.swappiness = 10 # 控制脏页堆积,避免回收前一次性回写把 IO 打满 vm.dirty_background_ratio = 5 vm.dirty_ratio = 20 # 关闭 zone 局部回收,NUMA 机器上容易引发跨节点抖动 vm.zone_reclaim_mode = 0文件名用90-前缀是让它排在系统默认配置之后,sysctl --system按文件名顺序加载,后面的覆盖前面的。麒麟V10 有些加固基线会在/etc/sysctl.conf里重新设置swappiness之类的项,改完务必sysctl -n逐个确认最终生效值,而不是看文件里写了什么。
# 加载并逐项确认最终生效值 sysctl --system sysctl -n vm.min_free_kbytes vm.vfs_cache_pressure vm.swappiness \ vm.dirty_background_ratio vm.dirty_ratio vm.zone_reclaim_mode4.4 THP 与 KSM:两个容易被忽略的内存「隐形消费者」
透明大页在 4.19 内核上默认策略通常是always,khugepaged内核线程会持续扫描并合并大页,这个过程本身占 CPU,合并出来的 2M 页在内存碎片化时又难以回收,表现出来就是「内存降不下去且 CPU 有莫名占用」。数据库类服务一般建议改成madvise,由应用自己决定要不要大页,Java 服务则看堆大小和 GC 类型,大堆配大页收益明显,小堆反而添乱。
KSM 是内存页合并,虚拟化场景下很有用,但它会把看起来「空闲」的页合并起来,导致内存统计和直觉不符,而且合并过程消耗 CPU。如果不是跑虚拟机或者容器密度极高的场景,建议确认ksmtuned是否在跑,不需要就停掉。
# 查看 THP 当前策略:[always] madvise never 中被方括号括住的是生效值 cat /sys/kernel/mm/transparent_hugepage/enabled cat /sys/kernel/mm/transparent_hugepage/defrag # 临时切到 madvise,重启后失效 echo madvise > /sys/kernel/mm/transparent_hugepage/enabled echo madvise > /sys/kernel/mm/transparent_hugepage/defrag # 确认 KSM 是否开启,0 表示关闭 cat /sys/kernel/mm/ksm/runecho madvise这种临时改动重启就没了,持久化要么写一个开机执行的 systemd unit,要么用tuned的自定义 profile。麒麟V10 上tuned通常可用,tuned-adm list能列出预置策略,把 THP 设置塞进 profile 里比散落的开机脚本好维护。这一块不建议在业务高峰做,改完最好重启一次相关服务,让进程重新建立自己的内存映射。
5. 避坑与排查:五次翻车记录,从「跑了没效果」到「IO 打满」
前面三章讲的是应该怎么做,这一章讲实际做的时候会在哪儿摔。下面五条都是现场遇到过的,按「现象 → 原因 → 解决」写,每条都对应一个具体的判断动作,不是泛泛的注意事项。
5.1 定时任务按时执行,内存一点没降
现象:journalctl -t mem-reclaim里能看到「开始回收」和「回收完成」,但回收后MemAvailable只涨了几十兆,第二天照旧告警。
原因:占用根本不是 page cache。这时候Cached可能只有几百兆,大头在AnonPages或Shmem上,drop_caches对这两类完全无效。
解决:回收前后各抓一次/proc/meminfo做差,看哪几个字段掉了。如果AnonPages不动,转去ps --sort=-rss找进程,用pmap -x <pid> | tail -5看它的内存段分布;如果Shmem不动,df -h | grep tmpfs找挂载点,再lsof +D /dev/shm看谁还拿着已删除文件的句柄。这种情况下定时回收脚本要保留(它对缓存仍然有效),但真正的处置是修进程或加内存。
5.2 回收一执行,磁盘 util 直接冲到 100%
现象:内存曲线是好看了,但每次回收时间点前后一分钟,监控上磁盘%util顶到 100,业务接口的 P99 从 80ms 涨到 800ms。
原因:两个叠加因素。一是回收前脏页没落盘,drop_caches触发了集中回写;二是回收把业务正在反复读的热数据缓存也清掉了,下一波请求全部穿透到磁盘。
解决:脚本里sync必须在drop_caches之前,且sync之后留一两秒。回收动作只写1,不动 dentry。把定时窗口挪到业务低峰,RandomizedDelaySec加大到 300 秒以上,别让多台机器同时动手。有条件的话先blockdev --getra看看预读设置,回收后适当提高预读能缓解穿透带来的延迟。
5.3 内存越回收越多,slabtop 里有个对象一路狂涨
现象:SUnreclaim从 300M 涨到 3G,drop_caches完全无效,slabtop里某一类缓存条目数持续增加。
原因:内核态内存问题。常见于驱动、文件系统模块、或者长期运行的内核对象没有正确释放,属于真正的泄漏,用户态手段无效。
解决:先记录slabtop -o -s c的输出,隔几小时再抓一次做对比,确认是哪个缓存对象在涨。然后对照内核版本查已知问题,麒麟V10 的 SP 版本更新通常包含内核修复,评估后升级。短期能做的只有定期重启相关服务或整机,且要在变更窗口里做,别指望定时脚本。
5.4 手动执行一切正常,cron 里就是没反应
现象:bash /usr/local/sbin/mem-reclaim.sh手敲能跑,日志也有;放进/etc/cron.d/之后一条记录都没有。
原因:九成是三个问题之一——文件格式少了用户字段、PATH没声明导致命令找不到、SELinux 上下文不对被拒绝执行。少数情况是 cron 服务本身没起。
解决:先systemctl status crond确认服务在跑,再看/var/log/cron有没有这个任务的调度记录。有调度无输出就查PATH和权限;ls -Z看上下文,用restorecon -v /etc/cron.d/mem-reclaim修。另外注意/etc/cron.d/下的文件不能有可执行权限,也不允许文件名带点号,这两个坑都很隐蔽。
5.5 出问题想查历史数据,发现 sar 和 journal 都是空的
现象:内存告警之后想回溯前两天的趋势,sar -r报「Cannot open /var/log/sa/saXX」,journalctl只能看到当次启动之后的日志。
原因:sysstat默认采集是关闭的,需要改/etc/sysconfig/sysstat里的ENABLED="true";journald 默认Storage=auto,/var/log/journal目录不存在时只把日志写内存,重启即丢。
解决:两处都改。sysstat改完systemctl restart sysstat,第二天确认/var/log/sa/下有当天的文件;journald 建目录mkdir -p /var/log/journal后systemctl restart systemd-journald,再用journalctl --disk-usage看占用,配合/etc/systemd/journald.conf里的SystemMaxUse=限制总量,别让它把根分区吃掉。这两件事属于典型的一次做完长期受益,不做的话每次排障都是黑匣子。
6. 回收到底有没有用:三天观测的自证方法
判断一套回收方案是否有效,不看内存曲线好不好看,看三个东西:回收前后的MemAvailable差值、回收时间点的 IO 延迟、以及业务侧的 P99 有没有变化。我一般在测试环境跑满三天再上生产,每天固定时间抓一遍数据,用下面这套最小指标集对比。
| 指标 | 采集方式 | 判读标准 |
|---|---|---|
| MemAvailable 占比 | sar -r的%available列 | 回收后回升 10 个百分点以上才算有效 |
| 回收触发次数 | journalctl -t mem-reclaim -p warning | 一天超过 8 次说明容量或参数有问题 |
| 磁盘 await | iostat -x 1 3的await列 | 回收窗口内不应超过基线 2 倍 |
| 换页速率 | sar -W的pswpin/pswpout | 长期为 0 说明没在啃 swap,符合预期 |
| 不可回收 slab | slabtop -o -s c的SUnreclaim | 三天内涨幅超过 20% 要单独排查 |
采集脚本不用复杂,一条sar -r -f /var/log/sa/sa$(date +%d) >> /var/log/mem-trend.log加进日常巡检就够了,关键是坚持。我吃过一次亏:脚本上线后只看free的曲线觉得效果很好,两周后业务反馈夜间批处理变慢,回头翻iostat才发现每次回收都伴随一次明显的写 IO 尖峰,只是白天没人注意。从那以后我每次上线这类回收脚本,都强制走一遍「三天 sar 对比 + 一次低峰期压测」,确认回收窗口内await和 P99 都没有明显劣化才放行。
这份资源里的脚本、unit 文件和 sysctl 配置都是按上面这套逻辑写好的,路径和参数照着改就能用,先在自己的测试机上跑通再推到生产。希望帮到你。
本文还有配套的精品资源,点击获取