简介:本资源面向银河麒麟V10系统运维工程师与国产化平台系统管理员,聚焦生产环境中常见的内存不释放(内存泄漏)问题,提供轻量、可落地的定时清理与监控解决方案。压缩包共3个文件(2个Shell脚本+1个说明文本),总大小仅2KB:其中freemem.sh用于定期释放缓存与清理无用内存页,task.sh实现基于crond的自动化调度,配置说明.txt则详细指导脚本部署、权限设置及参数调优方法,适配麒麟V10 SP1/SP2等主流版本。目前已有777人学习下载,资源虽小但直击运维痛点——无需编译工具、不依赖第三方库,开箱即用,兼顾快速应急与长期监控需求;同时附带典型场景下的执行逻辑与风险提示,帮助读者理解内存管理底层机制,提升自主排障与系统稳定性保障能力。
1. 银河麒麟V10内存“假泄漏”:不是程序真吃内存,而是内核缓存不主动归还——运维老手都踩过的定时释放盲区
你有没有遇到过这样的场景:某台银河麒麟V10服务器跑着几个Java服务和Nginx,top里看%MEM一路涨到85%,free -h显示available只剩1.2G,但ps aux --sort=-%mem | head -5加起来才占不到3G?重启服务没用,sync && echo 3 > /proc/sys/vm/drop_caches手动清一次能回落2G,可两小时后又回到警戒线——这不是内存泄漏,是Linux内核的page cache和slab缓存“太懂事”,默认不主动释放,而银河麒麟V10(基于Linux Kernel 4.19 LTS)沿用了这一策略。它本意是提升I/O性能,但在长期运行、小内存(≤16G)、高读写日志场景下,反而成了运维监控告警的常客。这份资源不是教你怎么改内核参数,而是提供一套可验证、可调度、可审计的定时内存释放方案:含systemd timer服务模板、带阈值判断的Shell脚本、释放前后内存快照日志、以及最关键的——如何避免误杀关键缓存导致业务抖动。适合所有在国产化环境中负责系统稳定性的运维工程师、信创项目交付工程师、以及需要通过等保/密评内存基线检查的技术人员。
2. 内存释放机制选型:为什么不用echo 3 > /proc/sys/vm/drop_caches裸命令,而要封装成带条件判断的服务?
2.1 Linux内核缓存类型与drop_caches的真实影响面
/proc/sys/vm/drop_caches接受0、1、2、3四个值,但实际生产中只用1和3:
echo 1 > /proc/sys/vm/drop_caches:仅清理page cache(文件缓存),对数据库、日志轮转类服务影响最小;echo 3 > /proc/sys/vm/drop_caches:同时清理page cache + dentries/inodes(目录项和索引节点缓存),会显著增加后续文件路径解析开销,Nginx静态资源访问、Java应用类加载可能瞬时变慢。
提示:
drop_caches不会释放应用程序占用的堆内存(RSS),只影响内核管理的缓存。available字段下降主因是page cache膨胀,而非进程泄漏。
在银河麒麟V10中,vm.vfs_cache_pressure=100(默认值)已足够平衡dentry/inode回收,因此日常巡检只需清理page cache,避免无差别执行echo 3。这是本方案选择echo 1为默认动作的根本原因。
2.2 为什么必须加阈值判断?——避免“越清越卡”的负反馈循环
直接定时执行echo 1 > /proc/sys/vm/drop_caches是典型反模式。某次模拟项目X中,某公司按每30分钟执行一次,结果发现:
- 清理前
available为2.1G(总内存16G),清理后升至3.8G; - 但15分钟后
available跌回1.9G,且pgpgin/s(每秒换入页数)飙升3倍; - 原因:强制清空page cache后,所有磁盘读操作被迫走真实I/O,大量小文件读(如Java classloader、Nginx access.log轮转)触发密集磁盘寻道,CPU iowait从2%冲到35%。
因此,脚本必须内置内存水位判断逻辑:仅当available低于设定阈值(如总内存的15%)且持续2个周期(防瞬时抖动)时才触发清理。这比单纯cron调度更符合运维实际。
2.3 封装为systemd服务的核心优势:可依赖、可日志、可状态追踪
相比传统crontab,systemd timer具备三大不可替代性:
- 依赖控制:可设置
After=network.target,确保网络就绪后再检查(避免日志上报失败); - 日志绑定:
journalctl -u kylin-mem-cleaner.service可查每次执行的输入输出、退出码、耗时; - 状态隔离:
systemctl is-active kylin-mem-cleaner.timer返回active或inactive,比ps aux | grep cron更精准。
下面给出完整service+timer定义,已适配银河麒麟V10的systemd v239版本:
# /etc/systemd/system/kylin-mem-cleaner.service [Unit] Description=Kylin V10 Memory Cache Cleaner Documentation=https://example.com/kylin-mem-cleaner After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/kylin-mem-cleaner.sh # 关键:禁止缓存导致的OOM Killer误杀 OOMScoreAdjust=-900 # 确保环境变量一致(尤其LANG) Environment="LANG=zh_CN.UTF-8" # 记录标准输出到journal StandardOutput=journal StandardError=journal # 超时设为30秒,避免卡死 TimeoutSec=30 [Install] WantedBy=multi-user.target# /etc/systemd/system/kylin-mem-cleaner.timer [Unit] Description=Run Kylin V10 Memory Cleaner every 2 hours Requires=kylin-mem-cleaner.service [Timer] # 每2小时触发一次,但首次启动后延迟5分钟(错峰) OnBootSec=5min OnUnitActiveSec=2h # 随机延迟120秒,避免集群内所有节点同时刷cache RandomizedDelaySec=120 [Install] WantedBy=timers.target注意:
OnBootSec=5min确保系统启动后5分钟再首次执行,避开开机自检高峰;RandomizedDelaySec是银河麒麟V10 systemd timer的原生支持特性,无需额外脚本实现。
3. 核心脚本实现:带内存水位检测、多级日志、安全防护的Shell脚本
3.1 脚本主体逻辑与参数说明
以下脚本保存为/usr/local/bin/kylin-mem-cleaner.sh,需chmod +x。它不依赖Python或awk高级特性,纯bash实现,兼容银河麒麟V10默认的dash/bash混合环境:
#!/bin/bash # kylin-mem-cleaner.sh - Galaxy Kylin V10 memory cache cleaner # Version: 1.2 (2024-Q3 stable) # =============== 配置区 =============== # 总内存阈值百分比(当available < total * THRESHOLD_PCT% 时触发) THRESHOLD_PCT=15 # 连续触发次数(需连续N次低于阈值才执行清理,防抖动) TRIGGER_COUNT=2 # 日志文件路径(建议挂载到独立分区,避免填满/) LOG_FILE="/var/log/kylin-mem-cleaner.log" # 缓存清理级别:1=page cache only, 3=page+dentries+inodes DROP_LEVEL=1 # 是否启用日志上报(0=禁用,1=启用,需提前配置rsyslog转发) ENABLE_LOG_REPORT=0 # ====================================== # 初始化计数器文件(首次运行自动创建) COUNTER_FILE="/var/run/kylin-mem-cleaner.counter" if [[ ! -f "$COUNTER_FILE" ]]; then echo "0" > "$COUNTER_FILE" fi # 获取当前available内存(KB),使用free命令的第二行(-/+ buffers/cache行已弃用,取Mem行) # 兼容free v3.3.10(麒麟V10默认) AVAILABLE_KB=$(free | awk 'NR==2{print $7}') TOTAL_KB=$(free | awk 'NR==2{print $2}') # 计算阈值(KB) THRESHOLD_KB=$((TOTAL_KB * THRESHOLD_PCT / 100)) # 记录时间戳和当前值 TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') LOG_ENTRY="[$TIMESTAMP] available=$AVAILABLE_KB KB, total=$TOTAL_KB KB, threshold=$THRESHOLD_KB KB" # 判断是否低于阈值 if [[ $AVAILABLE_KB -lt $THRESHOLD_KB ]]; then # 读取计数器并递增 CURRENT_COUNT=$(cat "$COUNTER_FILE") NEW_COUNT=$((CURRENT_COUNT + 1)) echo "$NEW_COUNT" > "$COUNTER_FILE" # 达到触发次数则执行清理 if [[ $NEW_COUNT -ge $TRIGGER_COUNT ]]; then # 执行清理前记录快照 echo "$LOG_ENTRY -> TRIGGERED (count=$NEW_COUNT)" >> "$LOG_FILE" # 记录清理前内存详情(free -h + slabinfo摘要) echo "=== BEFORE CLEAN ===" >> "$LOG_FILE" free -h >> "$LOG_FILE" echo "Slab info (top 5):" >> "$LOG_FILE" cat /proc/meminfo | grep -E "^(SReclaimable|Slab|Cached)" >> "$LOG_FILE" # 执行清理 echo "$DROP_LEVEL" > /proc/sys/vm/drop_caches 2>/dev/null # 等待1秒让内核完成 sleep 1 # 记录清理后快照 echo "=== AFTER CLEAN ===" >> "$LOG_FILE" free -h >> "$LOG_FILE" echo "Cleanup completed at $TIMESTAMP" >> "$LOG_FILE" # 重置计数器 echo "0" > "$COUNTER_FILE" # 可选:上报日志(需rsyslog配置UDP转发到中心日志服务器) if [[ $ENABLE_LOG_REPORT -eq 1 ]]; then logger -t "kylin-mem-cleaner" "Cache cleaned: available from ${AVAILABLE_KB}KB to $(free | awk 'NR==2{print $7}')KB" fi else echo "$LOG_ENTRY -> BELOW THRESHOLD (count=$NEW_COUNT, need $TRIGGER_COUNT)" >> "$LOG_FILE" fi else # 高于阈值,重置计数器 echo "0" > "$COUNTER_FILE" echo "$LOG_ENTRY -> ABOVE THRESHOLD" >> "$LOG_FILE" fi逻辑说明:脚本核心是
COUNTER_FILE状态文件,它将内存水位判断从“瞬时值”升级为“状态机”。TRIGGER_COUNT=2意味着需连续两次采样(间隔2小时)均低于阈值才行动,彻底规避日志轮转、备份任务等短时I/O高峰引发的误触发。free命令取NR==2(Mem行)是因银河麒麟V10的free输出格式固定为:第1行header,第2行Mem,第3行-/+ buffers/cache(已废弃),第4行Swap。
3.2 关键参数调优指南:不同场景下的推荐配置
| 场景描述 | 推荐THRESHOLD_PCT | 推荐TRIGGER_COUNT | 推荐DROP_LEVEL | 理由说明 |
|---|---|---|---|---|
| 16G内存,运行Tomcat+MySQL,日志量大 | 12 | 3 | 1 | MySQL自身有buffer pool,page cache冗余高;3次确认防日志切割抖动 |
| 32G内存,K8s节点,运行多个Pod | 8 | 2 | 1 | K8s kubelet对available敏感,需更激进;但Pod间隔离强,抖动影响小 |
| 8G内存,单机部署Nginx+PHP-FPM | 20 | 2 | 1 | 小内存机器page cache占比天然高,阈值需放宽;PHP opcode cache也占内存 |
| 等保三级要求:内存使用率≤70% | 30 | 1 | 1 | available≥30%即满足used≤70%,且等保检查通常看free -h的available列 |
提示:修改后需
systemctl daemon-reload并systemctl restart kylin-mem-cleaner.timer生效。
3.3 日志结构与审计要点:如何用日志反推内存问题根因
脚本生成的日志/var/log/kylin-mem-cleaner.log采用分段结构,每轮清理包含=== BEFORE CLEAN ===和=== AFTER CLEAN ===两个区块。审计时重点关注三类信息:
- 可用内存变化量:对比
BEFORE和AFTER的available值,若提升<500MB,说明page cache占比低,问题可能在slab或进程RSS; - Slab信息摘要:
SReclaimable(可回收slab)值若>1G,需进一步查slabtop -o | head -10定位高消耗slab对象(如ext4_inode_cache); - 触发频率:若连续3天每天触发>5次,表明存在持续性I/O压力源(如未优化的rsync备份、频繁tar打包),应排查源头而非仅清理缓存。
例如某次日志片段:
[2024-09-15 14:30:02] available=1824500 KB, total=16722800 KB, threshold=2508420 KB -> BELOW THRESHOLD (count=1, need 2) [2024-09-15 16:30:02] available=1792300 KB, total=16722800 KB, threshold=2508420 KB -> TRIGGERED (count=2) === BEFORE CLEAN === total used free shared buff/cache available Mem: 16330 9820 820 120 5689 1750 ... SReclaimable: 1245000 kB ... === AFTER CLEAN === total used free shared buff/cache available Mem: 16330 9820 820 120 5689 2280→available从1750MB升至2280MB,提升530MB,属正常page cache范围;但SReclaimable达1245MB,提示slab缓存偏高,应追加slabtop分析。
4. 避坑:银河麒麟V10内存释放的五个血泪经验(现象→原因→解决)
4.1 现象:执行echo 1 > /proc/sys/vm/drop_caches后,Nginx静态文件响应延迟突增300ms
原因:drop_caches清空了page cache,但Nginx配置了open_file_cache max=1000 inactive=20s,其内部文件句柄缓存未同步失效,导致每次请求需重新open()文件并触发磁盘I/O。
解决:在清理page cache后,追加nginx -s reload(非restart)以刷新open_file_cache;或在脚本中加入判断:pgrep nginx >/dev/null && nginx -s reload 2>/dev/null。
4.2 现象:定时任务执行后,journalctl -u kylin-mem-cleaner.service显示Failed with result 'timeout'
原因:TimeoutSec=30在高I/O负载下被突破,systemd强制kill进程,但/proc/sys/vm/drop_caches写入已生效,造成日志记录不全。
解决:将TimeoutSec提高至60,并在脚本开头添加ulimit -t 50(CPU时间限制),避免无限循环;同时echo "$LOG_ENTRY -> TIMEOUT RISK" >> "$LOG_FILE"留痕。
4.3 现象:free -h显示available回升,但top中%MEM仍居高不下
原因:%MEM计算基于RSS(常驻内存集),而available反映的是可立即分配的物理内存。Java应用的-Xmx堆内存即使未使用,也会锁定RSS,drop_caches对其无影响。
解决:区分监控指标——available用于判断系统级内存压力,RSS用于定位进程级内存占用;对Java服务,应结合jstat -gc <pid>看S0C/S1C/EC/OC使用率。
4.4 现象:脚本执行后,/var/log/kylin-mem-cleaner.log权限变为root:root,但/var/log目录属组为syslog,导致rsyslog无法写入
原因:脚本以root身份运行,创建日志文件时继承root权限,但rsyslog服务以syslog用户运行,无写权限。
解决:在脚本开头添加touch "$LOG_FILE" && chown root:syslog "$LOG_FILE" && chmod 644 "$LOG_FILE";或统一用logger替代文件写入,由rsyslog接管权限。
4.5 现象:集群中多台麒麟V10服务器同时触发清理,导致共享存储I/O队列暴涨,备份任务超时
原因:OnUnitActiveSec=2h未加随机延迟,所有节点在整点后2小时同步执行,形成I/O风暴。
解决:RandomizedDelaySec=120已在timer文件中配置,但需确认/etc/systemd/system.conf中DefaultTimeoutStartSec未被设为0(会禁用随机延迟);验证命令:systemctl list-timers --all | grep kylin,观察NEXT列时间是否分散。
5. 进阶验证:用/proc/meminfo字段交叉验证释放效果,建立可信基线
5.1 关键字段解读:哪些值真正反映page cache释放?
/proc/meminfo中与page cache直接相关的字段有三个,必须联合观察:
| 字段名 | 含义 | 释放page cache后变化趋势 | 是否可用于验证 |
|---|---|---|---|
Cached | page cache总量(含tmpfs) | 显著下降(降幅≈available提升量) | ✅ 强相关 |
SReclaimable | slab中可回收部分(dentry/inode等) | 轻微下降或不变(drop_caches 1不清理slab) | ⚠️ 仅作参考 |
MemAvailable | 内核估算的可立即分配内存 | 显著上升(与free命令available列一致) | ✅ 最终指标 |
提示:
Cached字段包含tmpfs内存(如/dev/shm),若业务使用tmpfs,其大小会干扰判断。验证时应先df -h /dev/shm确认tmpfs用量稳定。
5.2 构建自动化验证脚本:每小时快照/proc/meminfo关键字段
为建立长期基线,可在同一服务器部署轻量快照脚本,与清理脚本解耦:
#!/bin/bash # /usr/local/bin/meminfo-snapshot.sh SNAPSHOT_DIR="/var/log/meminfo-snapshots" DATE=$(date '+%Y%m%d') HOUR=$(date '+%H') mkdir -p "$SNAPSHOT_DIR/$DATE" # 仅提取关键字段,减少IO awk '/^Cached:|^SReclaimable:|^MemAvailable:/ {printf "%s %s %s\n", $1, $2, $3}' /proc/meminfo \ > "$SNAPSHOT_DIR/$DATE/meminfo_$HOUR.txt" # 每日压缩归档(保留7天) if [[ $(date '+%H') == "00" ]]; then find "$SNAPSHOT_DIR" -name "*.txt" -mtime +7 -delete tar -C "$SNAPSHOT_DIR" -cf "$SNAPSHOT_DIR/${DATE}_snapshots.tar" "$DATE" 2>/dev/null gzip "$SNAPSHOT_DIR/${DATE}_snapshots.tar" fi配合systemd timer每小时执行一次,生成类似数据:
# /var/log/meminfo-snapshots/20240915/meminfo_14.txt Cached: 3245000 kB SReclaimable: 1245000 kB MemAvailable: 1750000 kB5.3 建立可信基线:用历史数据识别“健康波动”与“异常增长”
收集7天快照后,用以下命令生成基线报告:
# 统计每日Cached峰值与均值(单位MB) for day in /var/log/meminfo-snapshots/202409*; do if [[ -d "$day" ]]; then date_str=$(basename "$day") peak_cached=$(awk '/^Cached:/ {print int($2/1024)}' "$day"/meminfo_*.txt 2>/dev/null | sort -nr | head -1) avg_cached=$(awk '/^Cached:/ {sum+=$2/1024; count++} END {printf "%.0f", sum/count}' "$day"/meminfo_*.txt 2>/dev/null) echo "$date_str: peak=$peak_cached MB, avg=$avg_cached MB" fi done | sort输出示例:
20240910: peak=3245 MB, avg=2810 MB 20240911: peak=3252 MB, avg=2815 MB 20240912: peak=3260 MB, avg=2820 MB 20240913: peak=4120 MB, avg=3650 MB ← 异常日! 20240914: peak=3248 MB, avg=2812 MB→20240913日Cached峰值突增27%,结合当日kylin-mem-cleaner.log发现该日触发了12次清理(平时2-3次),可断定存在异常I/O源(后经查为某备份脚本未加ionice导致)。
从那以后我每次上线新定时任务,都强制走一遍meminfo-snapshot.sh基线采集流程,并把Cached日均值纳入CMDB资产属性——它比free -h的瞬时值更能反映系统真实负载。希望帮到你。
本文还有配套的精品资源,点击获取