Redis 内存防崩溃优化方案
目标:让 Redis 永远有硬上限,系统永远不进 Swap,事故提前告警而非事后重启。
⚠️ 全文命令中的密码已替换为占位符你的密码,执行时请换成真实密码;redis-cli 路径每台服务器不同,请先按【附一】查询替换。
附一、先查询 redis-cli 路径(每台服务器不同,纯查询、不影响系统)
下面都是只读查询命令,不会改动任何东西,放心执行:
# 1. 看系统 PATH 里是否已能直接调用(最常用)
which redis-cli
# 2. 看正在运行的 redis-server 二进制在哪,redis-cli 通常在同目录的 bin 下
ps -ef | grep redis-server
# 3. 常见安装目录直查(哪个有输出就用哪个)
ls -l /usr/local/redis/bin/redis-cli 2>/dev/null
ls -l /usr/bin/redis-cli 2>/dev/null
ls -l /opt/redis/bin/redis-cli 2>/dev/null
# 4. 全盘找(只读扫描,不影响系统;结果里取第一个存在的路径)
find / -name redis-cli -type f 2>/dev/null | head查到后,把下文命令里的 /usr/local/redis/bin/redis-cli 整体换成你查到的实际路径即可。
一、根因结论(一句话)
Redis 没有有效的内存上限(或上限过大)→ 内存无限增长 → 物理内存耗尽 → Swap 打满(100%) → 页面置换风暴 → CPU 99% → 系统假死。
典型崩溃时间线:Redis 吃满内存 → Swap 96% → Swap 100% → 触发事件,CPU 2%→42% → CPU 99% / 高负载 / 假死 → 人工重启。
核心矛盾不是"Redis 用了多少",而是"它能不能无限用"。 只要给它一个硬上限并留足系统余量,同样的负载绝不会再把机器拖死。
二、设计三原则
| # | 原则 | 作用 |
|---|---|---|
| 1 | 给 Redis 设硬上限 maxmemory | 满则"驱逐旧数据",而非"撑爆系统内存" |
| 2 | 系统永远留余量,绝不进 Swap | Swap 一旦打满,Redis 延迟暴涨、CPU 被置换吞掉 |
| 3 | 合理监控水位,提前感知风险 | 把事故消灭在苗头,而不是 100% 假死 |
说明:原则 2 的"绝不进 Swap"是靠原则 1 把 Redis 框在合理上限内实现的(详见第三节),不需要去动系统内核参数。
三、maxmemory 设置(最关键的一步)
3.1 计算公式(务必满足)
maxmemory ≤ (物理内存 × 0.85) − 其他进程常驻内存- 例:物理 31G,其他常驻进程(应用、数据库、Agent 等)合计约 6G。
- 安全上限:31 × 0.85 − 6 ≈ 20G。即 Redis 最多给到 20G 也不会逼系统用 Swap。
3.2 推荐值
- 按"当前工作集 × 1.5"留余量防突增。若当前稳定 8G,则
maxmemory = 12G(12884901888 字节)。 - 12G + 6G 其他 ≈ 18G,远小于 31G,系统永远有空闲 → 永不 Swap。
3.3 设置命令(路径请先按【附一】查询替换)
# 临时生效(立即止血)
/usr/local/redis/bin/redis-cli -a '你的密码' CONFIG SET maxmemory 12884901888
# 验证
/usr/local/redis/bin/redis-cli -a '你的密码' CONFIG GET maxmemory3.4 ⚠️ 持久化(极易踩坑)
若 redis 启动时没有带配置文件参数(如 redis-server 0.0.0.0:6379),它是用默认配置启动的,CONFIG REWRITE 可能新建文件但重启仍会丢配置。
必须找到真正的启动入口并写进去(以下为只读查询,定位用):
# 看是不是 systemd 托管
systemctl cat redis 2>/dev/null
systemctl cat redis-server 2>/dev/null
# 或看 init 脚本 / 启动脚本里有没有 redis-server
ls -l /etc/init.d/ | grep -i redis
grep -r "redis-server" /etc/rc.local /root/*.sh /home/*/*.sh 2>/dev/null找到后,在启动配置里显式加一行 maxmemory 12884901888(或指向一个含该项的 redis.conf),再重启验证 CONFIG GET maxmemory 是否持久生效。不要只依赖 CONFIG REWRITE。
四、淘汰策略(maxmemory-policy)选择
4.1 策略对比
| 策略 | 淘汰范围 | 适用场景 | 风险 |
|---|---|---|---|
noeviction | 不淘汰,写报错 | 不允许丢数据 | 写阻塞,不可用 |
allkeys-lru | 全部 key,按最近最少用 | 纯缓存 | 永久 key 也会被删 |
allkeys-lfu | 全部 key,按最不常用 | 纯缓存(更优) | 永久 key 也会被删 |
volatile-lru/lfu | 仅有 TTL 的 key | 缓存+持久混放 | 无 TTL 的 key 永不淘汰,可能涨爆 |
volatile-ttl | 仅即将过期的 key | 同 volatile | 同上 |
选型建议:纯缓存实例用allkeys-lfu(比 LRU 更懂"常用",缓存命中更好);若缓存与不可重建的持久数据混放,应改用volatile-lfu并给缓存 key 补 TTL,或干脆把持久数据拆到独立实例。
五、OOM 防护(可选兜底,保护关键进程)
本节为"最后兜底",非必须;主防线仍是 maxmemory + 不进 Swap。以下只是调整 OOM 优先级(不改内核行为),风险低。
万一再次发生内存争抢,确保关键业务进程(数据库 / 应用)不被 OOM killer 误杀,而让 Redis 优先被回收:
# 降低关键进程的 OOM 被杀概率(数值越小越安全,-1000 几乎永不被杀)
echo -500 > /proc/<关键进程_pid>/oom_score_adj
# 提高 redis 被杀优先级(让它先被回收,而不是拖死整机)
echo 500 > /proc/<redis_pid>/oom_score_adj # 换成实际 redis PID六、架构层面(长期根治,均为建议、非强制命令)
- 缓存 / 持久数据分离:Redis 只放可重建的缓存;Session、锁、业务状态搬到其他存储或独立实例。
- 服务器图形界面:生产服务器通常不需要图形界面,如确认无用,可禁用其自启动以省内存(不要盲目卸载,避免牵连依赖)。
- 多实例隔离:若多种业务共用一台 Redis,按业务拆实例,单实例故障域更小、上限更好控。
七、应急响应 Runbook(若再次假死)
- 不要反复重启:重启会重新加载/重建数据,可能二次冲击。先确认 Swap 状态。
快速查看(只读):
free -h; swapon --show # 看 Swap 是否打满- 若 Redis 已无响应且内存失控:可
redis-cli SHUTDOWN NOSAVE(避免写坏 RDB),再按本方案设好 maxmemory 后拉起。 - 拉起后第一时间
CONFIG GET maxmemory+INFO memory验证上限已生效。
八、落地检查清单
- [ ] 先按【附一】查到本机 redis-cli 真实路径,替换全文命令
- [ ] 确认缓存实例是否纯缓存(决定 allkeys-lfu 还是 volatile-lfu)
- [ ]
CONFIG SET maxmemory 12884901888(12G,立即生效) - [ ] 找到 redis 真实启动入口,把 maxmemory 写进配置/启动参数(持久化)
- [ ] 切换/确认
maxmemory-policy(纯缓存→allkeys-lfu) - [ ] 配置 OOM 优先级,保护关键进程(可选)
- [ ] 验证:重启 redis 后
CONFIG GET maxmemory仍为 12G
附:命令安全提示
命令行用 -a 明文密码会触发告警且密码进 history。建议改用环境变量(把 你的密码 换成真实密码):
export REDISCLI_AUTH='你的密码'
/usr/local/redis/bin/redis-cli CONFIG GET maxmemory附:检查脚本
#!/bin/bash
# redis_health_check.sh
# 用途:巡检 Redis 内存水位 + 系统 Swap,超标即打印告警(可接邮件/钉钉)
# 用法:crontab 每 5 分钟执行一次,输出追加入日志
# */5 * * * * /usr/local/redis/redis_health_check.sh >> /var/log/redis_health.log 2>&1
# ===== 配置区(按需修改)=====
# 密码:执行前请换成真实密码(或改用 export REDISCLI_AUTH 方式,避免明文)
PASS='你的密码'
HOST=127.0.0.1
PORT=6379
# redis-cli 路径:每台服务器不一样,优先用 which 自动查找;找不到再手动改成实际路径
# 也可先手动查询: which redis-cli
# ls -l /usr/local/redis/bin/redis-cli 2>/dev/null
# ps -ef | grep redis-server
# find / -name redis-cli -type f 2>/dev/null | head
REDIS_CLI=$(command -v redis-cli 2>/dev/null)
if [ -z "$REDIS_CLI" ]; then
REDIS_CLI="/usr/local/redis/bin/redis-cli" # 自动找不到时,改成你查到的真实路径
fi
TS=$(date '+%Y-%m-%d %H:%M:%S')
echo "===== [$TS] Redis 健康巡检 ====="
# ---- 1. Redis 内存使用率 ----
USED=$($REDIS_CLI -h $HOST -p $PORT -a "$PASS" INFO memory 2>/dev/null | awk -F: '/^used_memory:/{print $2}' | tr -d '\r')
MAX=$($REDIS_CLI -h $HOST -p $PORT -a "$PASS" INFO memory 2>/dev/null | awk -F: '/^maxmemory:/{print $2}' | tr -d '\r')
EVI=$($REDIS_CLI -h $HOST -p $PORT -a "$PASS" INFO stats 2>/dev/null | awk -F: '/^evicted_keys:/{print $2}' | tr -d '\r')
if [ -n "$MAX" ] && [ "$MAX" -gt 0 ] 2>/dev/null; then
RATIO=$(awk "BEGIN{printf \"%.2f\", $USED/$MAX*100}")
echo "Redis 内存使用率: ${RATIO}% (used=$USED max=$MAX)"
# 超过 85% 逼近 maxmemory,即将触发驱逐
if awk "BEGIN{exit !($RATIO>85)}"; then
echo " [告警] 内存使用率大于85%,逼近 maxmemory,即将触发 key 驱逐!"
fi
else
echo " [错误] maxmemory 未设置或为 0 —— Redis 可无限吃内存,存在崩溃风险!"
fi
echo "累计驱逐 key 数 (evicted_keys): ${EVI:-未知}"
# ---- 2. 系统 Swap 使用率 ----
SWAP_TOTAL=$(free | awk '/Swap:/{print $2}')
SWAP_USED=$(free | awk '/Swap:/{print $3}')
if [ -n "$SWAP_TOTAL" ] && [ "$SWAP_TOTAL" -gt 0 ] 2>/dev/null; then
SWAP_RATIO=$(awk "BEGIN{printf \"%.2f\", $SWAP_USED/$SWAP_TOTAL*100}")
echo "系统 Swap 使用率: ${SWAP_RATIO}%"
if awk "BEGIN{exit !($SWAP_RATIO>50)}"; then
echo " [严重] Swap 使用率大于50%,系统已进入危险区,立即排查内存占用!"
fi
else
echo "系统未启用 Swap(或未检测到)—— 无需关注 Swap 水位"
fi
echo ""
评论 (0)