简介面向银河麒麟V10服务器运维场景的轻量脚本包聚焦系统长时间运行后内存不释放、可用内存逐步下降的问题适合需要快速落地定时清理与日常巡检方案的中初级运维人员参考。压缩包仅2KB共3个文件由2个sh脚本和1个txt配置说明组成脚本承担定时任务与内存释放逻辑文本文件用于说明参数配置和使用注意体量小巧便于在测试或生产环境前审阅。已有787人学习下载说明该问题在国产化服务器运维中具有一定普遍性。内容围绕“定时”这一核心思路给出可复用的脚本组织方式与配置说明读者可据此理解如何将内存清理纳入计划任务、结合系统监控判断触发时机并保留配置记录以便后续排查同时可作为内存泄漏初步缓解与运维巡检的参考素材帮助减少人工干预频率并提升问题定位效率。1. 内存明明闲着服务却被 OOM 干掉一台跑麒麟 V10 的生产机free -m里available还有十几个 G半夜业务进程还是被 OOM killer 干掉了。运维第一反应通常是挂个定时任务清缓存——这条路能走通但只在特定条件下有效清错对象就是白忙。这篇记录我把麒麟 V10 上内存不释放这件事拆开先用free和/proc/meminfo分清可回收缓存和真泄漏再给一份带阈值判断的定时回收脚本能直接挂 cron 或 systemd timer最后说清楚哪些场景下drop_caches根本救不了你。适合管着几台到几十台国产 Linux 服务器、被内存高告警追着跑的运维。2. 先分清可回收缓存和真泄漏free 与 /proc/meminfo 的读法在麒麟 V10 上判断内存状态free只能当第一眼/proc/meminfo才是定案依据。顺序反了后面定时任务写得再漂亮也是在错误的目标上使劲。2.1 free -m 里哪一列才是能用的内存先看一份典型输出这是我一般让同事贴过来的第一条命令free -m # total used free shared buff/cache available # Mem: 32011 18420 1203 412 22388 12960 # Swap: 8191 512 7679free只有 1.2G很多人到这一步就开始紧张了实际上真正的判据是最后一列available这里还有 12.9G。buff/cache那 22G 里绝大部分是文件页和 dentry/inode 缓存内核在进程要内存时会主动回收属于借出去还能要回来的部分不需要外部干预。shared列容易被忽略它统计的是 tmpfs 和 shmem 占用。这部分虽然也记在buff/cache里但属于匿名页性质drop_caches根本动不了它。见过有人把/dev/shm当临时目录写满然后反复执行清缓存脚本free一点都不涨最后查df -h /dev/shm才发现问题——这类属于典型的清错对象。available的估算口径大致是MemFree加上可回收的 page cache 和可回收 slab再减去内核水位等不能挪用的部分。所以要看的不是它绝对值高低而是它掉下去之后能不能自己回来。如果一小时内available从 12G 掉到 1G 且不再回升那才是真出事了。2.2 /proc/meminfo 里几个决定成败的字段free的信息都来自这里直接读原文件能拿到更细的拆分。我常备的一条命令awk /^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapCached|Slab|SReclaimable|SUnreclaim|AnonPages|Mapped|Dirty|Writeback|Shmem):/ /proc/meminfo各字段和能不能被清掉的关系整理成一张表更直观字段含义drop_caches 能否回收Cached文件页缓存含 shmem大部分可以shmem/tmpfs 部分不行Buffers块设备元数据缓存可以SReclaimable可回收 slab如 dentry、inode_cache可以对应drop_caches2SUnreclaim不可回收 slab不行AnonPages匿名页进程堆和栈不行只能 swap 或结束进程Mappedmmap 映射的文件页部分可以Dirty/Writeback脏页和正在回写的页必须先sync否则清不动Shmemtmpfs / 共享内存不行SReclaimable和SUnreclaim是分开的两项很多人只看Slab总和看到几十 G 就急着清。如果大头在SReclaimable说明是文件系统遍历留下的 dentry/inode清一次能下来如果大头在SUnreclaim那是内核模块或驱动自己申请的定时任务对它是零效果得从驱动版本上找原因。Dirty值得单独提一句。有些机器上Cached很高但清不下来是因为脏页还没回写内核不会把脏页当成可立即回收的对象。脚本里sync那一步不是仪式感是前置条件。2.3 用 slabtop 和 ps_mem 把增长源夹出来知道哪一类内存在涨之后下一步是定位到具体的 cache 名或进程。slab 侧看排行# 按缓存占用排序只看前 15 行 slabtop -o -s c | head -15如果dentry、inode_cache、ext4_inode_cache排在前三基本可以确定是文件遍历或大量小文件操作留下的drop_caches2是有效手段。如果看到的是kmalloc-*或某个特定驱动的 cache 长期占大头那就不是清理能解决的事。进程侧看排行ps_mem比ps aux准因为它按 PSS 统计共享库不会被重复计算# 仓库里有就用仓库的没有可以 pip3 install ps_mem本质是个单文件脚本 ps_mem | head -20再细一层用smaps_rollup看单进程的内存构成。内核 4.14 以上有smaps_rollup麒麟 V10 服务端自带的内核是满足的老一点的版本退回累加smapsPID$(pgrep -f your_service | head -1) if [ -r /proc/$PID/smaps_rollup ]; then grep -E ^(Rss|Pss|Private_Dirty|Anonymous): /proc/$PID/smaps_rollup else # 兜底从 smaps 累加代价是遍历全部 VMA慢但准 awk /^Rss:/{r$2} /^Pss:/{p$2} /^Anonymous:/{a$2} \ END{printf RSS%dMB PSS%dMB Anon%dMB\n, r/1024, p/1024, a/1024} /proc/$PID/smaps fi判断泄漏只需要一个动作隔一段时间采样Anonymous看它是否单调上涨。上涨就是应用自己的问题清缓存只会让free数字好看OOM 该来还是来。PID$(pgrep -f your_service | head -1) for i in $(seq 1 10); do awk -v t$(date %T) /^Anonymous:/{a$2} \ END{printf %s anon%.0fMB\n, t, a/1024} /proc/$PID/smaps sleep 60 done十分钟的曲线足够定性了。平坦或者锯齿状反复是正常一路向上不回头就是泄漏别再往定时清理上投入时间。2.4 一条判断链三种现象对应三种处置把这套东西压缩成一个决策顺序现场排查时按顺序走不要跳步观测到的现象最可能的原因处置方向buff/cache高available充裕无 OOM正常缓存不动最多调vfs_cache_pressureavailable掉到个位数SReclaimable同步上涨dentry/inode 堆积定时drop_caches2本文方案AnonPages单调涨某进程 RSS 同步涨应用内存泄漏定位进程cgroup 限额或重启清缓存无效Slab涨但SReclaimable不涨内核模块/驱动泄漏升级驱动或内核清缓存无效Shmem高df -h /dev/shm接近满tmpfs 被写满清理临时文件或调整 tmpfs 容量这张表的意义在于划边界只有前两类里涉及SReclaimable的那部分才是定时回收脚本的正当业务。把后面几类也交给定时任务就成了定期做无用功还容易把清过缓存了当成已经处理过的证据耽误真正的定位。3. 定时回收脚本阈值判断比 drop_caches 本身更重要3.1 手动验证一次再谈自动化写脚本之前先在测试机上手工跑一遍确认清理确实有效、幅度多大否则自动化只是把无效动作重复放大# 1. 记录清理前快照 awk /^(MemAvailable|Cached|SReclaimable|Slab):/ /proc/meminfo # 2. 刷脏页脏页没回写之前内核不会把它算作可回收 sync; sync # 3. 先回收 page cache echo 1 /proc/sys/vm/drop_caches # 4. 再回收 slab 里的 dentry/inode echo 2 /proc/sys/vm/drop_caches # 5. 再看一次快照对比 awk /^(MemAvailable|Cached|SReclaimable|Slab):/ /proc/meminfodrop_caches的三个取值含义不同1只清 page cache2只清可回收 slab3两者都清。之所以建议分两步写是因为分开执行能看出到底哪一类占了大头后续参数调整有依据。直接echo 3一次搞定也行但就丢掉了这个观测点。提示drop_caches是非破坏性操作只回收干净页和可回收 slab不会丢数据但会让热数据缓存全部失效下一波请求会集中打到磁盘上。生产机上不要在业务高峰期手工跑做完一次先观察十分钟 IO。3.2 脚本骨架加锁、判阈值、留日志最省事的写法是无条件定时清但生产环境里我一般会加阈值和并发锁避免它自己成为抖动源#!/bin/bash # /usr/local/sbin/mem_reclaim.sh set -u THRESHOLD${THRESHOLD:-15} # 可用内存占比低于该值才动手单位 % LOG${LOG:-/var/log/mem_reclaim.log} LOCK/var/run/mem_reclaim.lock # 并发锁上一次还没跑完就跳过避免短周期任务叠加 exec 9$LOCK flock -n 9 || { echo $(date %F %T) skip: previous run still running $LOG; exit 0; } # 老内核可能没有 MemAvailable用 MemFreeBuffersCached 兜底 read -r total avail $(awk /^MemTotal:/ {t$2} /^MemAvailable:/ {a$2; has1} /^MemFree:/ {f$2} /^Buffers:/ {b$2} /^Cached:/ {c$2} END{ if (!has) a f b c; printf %d %d, t, a } /proc/meminfo) [ $total -eq 0 ] { echo $(date %F %T) error: MemTotal unreadable $LOG; exit 1; } pct$(awk -v a$avail -v t$total BEGIN{printf %d, a*100/t}) echo $(date %F %T) total${total}kB avail${avail}kB pct${pct}% $LOG if [ $pct -lt $THRESHOLD ]; then sync sleep 1 # 给回写线程一点时间落盘 echo 1 /proc/sys/vm/drop_caches echo 2 /proc/sys/vm/drop_caches after$(awk /^MemAvailable:/{print $2} /proc/meminfo) echo $(date %F %T) reclaimed pct${pct}% avail_after$((after/1024))MB $LOG else echo $(date %F %T) above threshold, no action $LOG fi几个参数决定了它是帮忙还是添乱参数建议值说明THRESHOLD1015%低于此才清。设太高会频繁触发等于周期性冷启动执行周期3060 分钟10 分钟一次在生产上偏激进除非 IO 余量很大LOCK/var/run/下放 tmpfs重启自动清掉不会留下死锁文件LOG/var/log/下必须配 logrotate否则日志本身会变成另一个增长源阈值这条线怎么定我的习惯是先看历史曲线把available的周内最低值找出来取比它再低几个百分点的位置。这样正常运行期脚本基本不触发只有真正下滑时才介入drop_caches的执行次数一个月可能就几次对 IO 的影响可以忽略。3.3 挂 cron 还是 systemd timer两种都能用。麒麟 V10 上 cron 更通用systemd timer 的好处是能拿到执行状态、支持Persistent关机错过的任务开机补跑、日志直接进 journal。追求简单就选 cron# /etc/cron.d/mem_reclaim # 放在 cron.d 下必须声明 SHELL 和 PATHcron 默认环境极简 SHELL/bin/bash PATH/sbin:/bin:/usr/sbin:/usr/bin */30 * * * * root /usr/local/sbin/mem_reclaim.sh /var/log/mem_reclaim.cron.log 21如果想要更可控的状态管理用 timer# /etc/systemd/system/mem-reclaim.service [Unit] DescriptionReclaim reclaimable page and slab cache under memory pressure Afternetwork.target [Service] Typeoneshot EnvironmentTHRESHOLD15 ExecStart/usr/local/sbin/mem_reclaim.sh# /etc/systemd/system/mem-reclaim.timer [Unit] DescriptionRun mem-reclaim every 30 minutes [Timer] OnBootSec10min OnUnitActiveSec30min AccuracySec1min Persistenttrue [Install] WantedBytimers.target启用和核对状态chmod 0755 /usr/local/sbin/mem_reclaim.sh systemctl daemon-reload systemctl enable --now mem-reclaim.timer systemctl list-timers --all | grep mem-reclaim journalctl -u mem-reclaim.service -n 20 --no-pager注意脚本必须以 root 运行/proc/sys/vm/drop_caches默认只有 root 可写。放在容器里跑的思路行不通容器内看到的/proc/sys通常是只读挂载要清也只能清宿主机。3.4 日志轮转和不定期回看这个脚本一旦上线就是长期存在的东西日志不轮转会慢慢把磁盘吃满讽刺的是它本来是用来救内存的# /etc/logrotate.d/mem_reclaim /var/log/mem_reclaim.log /var/log/mem_reclaim.cron.log { weekly rotate 8 compress missingok notifempty create 0640 root root }比日志轮转更重要的是定期回看日志里pct的分布。如果连续两周每次执行都是above threshold, no action说明这台机器根本不需要这个任务撤掉减少一层复杂度如果每次都在阈值线下、清理后avail_after只涨一点点说明清的东西不对回去看第 2 章的判断链。4. 定时清理的五个坑从清了没用到清了添乱这一章是血泪经验集中区。前面脚本能跑起来不难难的是跑起来之后别出岔子。4.1 现象手工执行有效cron 里毫无反应原因几乎都是环境差异。cron 的PATH只有/usr/bin:/bin脚本里如果用到了相对路径或者/usr/sbin下的命令就会失败另外cron.d下的文件权限和属主不对必须是 root:root且不能有组写权限cron 会直接忽略不报错。解决办法是把环境写死在任务文件里命令全部用绝对路径然后去看 cron 自己的日志确认到底有没有被调度# 麒麟 V10 上 cron 日志可能在 messages 或 journal 里两个都查一下 grep CROND /var/log/messages | tail -20 journalctl -u crond --since 1 hour ago --no-pager | tail -20如果日志里能看到执行记录但没有脚本自己的输出问题就在脚本内部直接bash -x手工模拟 cron 环境跑一遍最快。4.2 现象free 涨回来了业务进程照样被 OOM 杀这是最典型的翻车场景。drop_caches只动 page cache 和可回收 slab对AnonPages一点办法没有。而 OOM killer 的评分主要看进程的匿名页占用和 cgroup 限额跟free显示的剩余量关系不大。确认方式很直接看内核留下的现场dmesg -T | grep -i -E out of memory|killed process | tail -10输出里会带上被杀进程的total-vm、rss和当时的Mem-Info快照。如果anon-rss很大而file-rss很小说明压力全在匿名页上定时清缓存这条路从一开始就不适用得回到应用层查泄漏或者上 cgroup 限额。4.3 现象脚本报 Permission denied或写 drop_caches 没效果两个方向排查。一是权限脚本是不是真的以 root 跑的id打出来确认一下二是/proc的挂载属性容器或加固过的环境里/proc/sys可能是只读的mount | grep /proc # 输出里出现 ro 或者 /proc/sys 单独 ro 挂载就别在容器里折腾了还有一种更隐蔽的情况echo 3 /proc/sys/vm/drop_caches执行成功但Cached几乎不降。这时候看Dirty和Writeback如果脏页很多且磁盘 IO 已经饱和回写线程跟不上内核会拒绝回收这些页。先解决 IO 瓶颈再谈回收。4.4 现象加了定时任务之后业务接口的 P99 反而涨了清缓存本身不慢慢的是清完之后的那一波。所有热数据失效紧接着的请求全部穿透到磁盘如果底层是机械盘或者网络存储这一下能顶出几秒的抖动。周期越短这种周期性冷启动越频繁等于人为制造内存和 IO 的锯齿。处置办法是三条一起上把周期从 10 分钟拉到 3060 分钟把阈值从 30% 降到 1015%在脚本里记录清理前后的时间戳事后能和 APM 的毛刺对齐。如果业务对延迟极其敏感宁可让这台机器多留 2G 余量也不要靠高频清理硬撑。4.5 现象日志里 pct 恒为 0或者脚本报 unary operator expected前者是整数除法被截断可用内存占比不足 1% 时a*100/t会算成 0看起来像内存耗尽。后者多半是MemAvailable字段取不到——极老的内核没有这个字段awk输出空串后面比较就炸了。上面 3.2 的脚本里已经用has标志位做了兜底用MemFreeBuffersCached近似误差在可接受范围内。另外注意单位。/proc/meminfo全是 kB脚本里做展示时该除 1024 就除别把 kB 当 MB 报出去这种低级错误在值班交接时最容易误导人。5. 参数级止血把回收压力交给内核而不是定时任务定时清理是兜底手段不是首选。真正让内存该放就放的是几个内核参数的方向调整。麒麟 V10 上改这些走/etc/sysctl.d/别直接写/etc/sysctl.conf便于按场景拆分和回滚# /etc/sysctl.d/90-memory-reclaim.conf # dentry/inode 缓存更容易被回收适合小文件遍历多的场景 vm.vfs_cache_pressure 200 # 给内核留出更大的安全水位降低 direct reclaim 触发概率 vm.min_free_kbytes 1048576 # 尽量少用 swap但不要设 0留一点能缓解突发压力 vm.swappiness 10 # 脏页阈值收紧避免一次性堆积太多脏页阻塞回收 vm.dirty_ratio 15 vm.dirty_background_ratio 5应用并确认sysctl --system sysctl vm.vfs_cache_pressure vm.min_free_kbytes vm.swappiness参数不是越大越好。vfs_cache_pressure调到 200 意味着 dentry 和 inode 更容易被丢弃代价是频繁的文件元数据访问要反复重建文件服务类业务会明显变慢一般从 150 起试观察一周再动。min_free_kbytes设到 1G 对于 32G 内存的机器是合理的但 8G 小内存机上设这么高会直接吃掉可用内存按总内存的 2%3% 估比较稳。验证有没有效果别只看free。要看/proc/vmstat里的直接回收计数它才是内存紧张程度的真实指标# 每 10 秒采一次重点看 pgscan_direct 和 pgsteal_direct 的增量 vmstat -s | grep -E pages scanned|pages stolen for i in $(seq 1 6); do awk -v t$(date %T) /^pgscan_direct/ {sd$2} /^pgsteal_direct/{td$2} /^pgscan_kswapd/ {sk$2} END{printf %s direct_scan%d direct_steal%d kswapd_scan%d\n, t, sd, td, sk} /proc/vmstat sleep 10 done判断标准很干脆pgscan_direct持续快速增长说明进程申请内存时被逼着自己去回收压力已经传导到业务线程上了这时候调参数和加内存都比定时清理更对症如果只有pgscan_kswapd在动说明是后台线程在慢慢回收属于健康状态什么都不用做。还有一个我踩过的坑改完 sysctl 一定要做一次长时间对比至少覆盖一个业务高峰。有次在测试机上把vfs_cache_pressure调到 300 感觉很爽SReclaimable确实压下去了结果上线后文件索引服务的延迟翻了一倍回滚才恢复。从那以后我每次动内存相关参数都会先在一台低峰期可切的机器上跑满 24 小时把vmstat、sar -r和业务延迟三条曲线放在一起看确认没有副作用再推到其他机器——内存这个黑匣子宁可慢一点。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?