Redis 突然只能读不能写:MISCONF 报错的 3 类触发条件与 4 步恢复

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [Redis 突然只能读不能写:MISCONF 报错的 3 类触发条件与 4 步恢复](#Redis 突然只能读不能写:MISCONF 报错的 3 类触发条件与 4 步恢复)
    • 一、这条报错到底在说什么
    • [二、3 类触发条件](#二、3 类触发条件)
      • [条件 1:磁盘写不进去(最常见)](#条件 1:磁盘写不进去(最常见))
      • [条件 2:fork 失败(容器里高频)](#条件 2:fork 失败(容器里高频))
      • [条件 3:你连的是只读从节点(报错不同,但现象一样)](#条件 3:你连的是只读从节点(报错不同,但现象一样))
    • 三、命令清单
    • [四、4 步恢复(第 2 步最容易被做错)](#四、4 步恢复(第 2 步最容易被做错))
    • 五、配置模板(防再犯)
    • 六、总结

Redis 突然只能读不能写:MISCONF 报错的 3 类触发条件与 4 步恢复

线上 Redis 一点征兆没有,突然所有写入都失败了,客户端抛的是这一句:

text 复制代码
MISCONF Redis is configured to save RDB snapshots, but it is currently not able
to persist on disk. Commands that may modify the data set are disabled.
Please check Redis logs for details about the error.

读取完全正常,只有写入被拒。更让人困惑的是:Redis 明明是内存数据库,为什么写不进去要怪「磁盘」?

答案在最后一句配置里:Redis 为了保护持久化的一致性,在 RDB 快照失败时会主动关闭写命令 。这不是 Redis 坏了,是一道保护机制起作用了------它宁可让你写不进去,也不愿意让你以为数据已经落盘、实际上没有。

这篇文章讲清这道保护机制的 3 类触发条件,以及真正正确的 4 步恢复动作(其中第 2 步在网上被大量错误地当成「解决方案」)。

一、这条报错到底在说什么

触发逻辑只有一条:

text 复制代码
Redis 配置了 RDB 快照(save 指令)
        ↓
stop-writes-on-bgsave-error = yes(默认值,生产建议保持 yes)
        ↓
最近一次 BGSAVE 失败了
        ↓
所有可能修改数据集的命令被拒绝(SET/DEL/INCR/LPUSH...)
GET 之类的读命令不受影响

所以排查的第一步不是 改配置,而是去问:上一次 BGSAVE 为什么失败?

bash 复制代码
# 看持久化状态,重点看这两个字段
redis-cli INFO persistence
# rdb_last_bgsave_status:err     ← 上一次快照失败
# rdb_last_save_time:1696300000  ← 最后一次成功的时间戳

二、3 类触发条件

条件 1:磁盘写不进去(最常见)

BGSAVE 要把内存里的数据写成 RDB 文件,写不进去的常见原因:

  • 磁盘满了 (df -h 一看就知道,但往往是别的进程把盘占满了);
  • 目录没有写权限 (Redis 进程用户 ≠ dir 的所有者,或者用了只读挂载);
  • dir 配置指向了一个不存在的路径(改过配置文件但没建目录);
  • 磁盘配额(quota)或 inode 用尽 (df -i)。

日志里通常长这样:

text 复制代码
# Write error saving DB on disk: No space left on device
# Error trying to save the DB, can't exit.

条件 2:fork 失败(容器里高频)

BGSAVE 靠 fork() 子进程来做快照。fork 失败时日志是:

text 复制代码
# Can't save in background: fork: Cannot allocate memory

两个高频成因:

  1. vm.overcommit_memory = 0:内核保守估计,觉得 fork 一个和父进程一样大的进程可能超内存,直接拒绝。Redis 官方明确要求设成 1:
bash 复制代码
sysctl vm.overcommit_memory=1
echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf
  1. 容器/cgroup 限制:Redis 在容器里,fork 瞬间的内存需求超过 cgroup limit(哪怕实际用的是 Copy-On-Write,内核在限定时仍可能按最坏情况判断)。这种场景要么放宽容器内存,要么把 Redis 放到内存更充裕的节点上。

顺带一个相关现象:INFO stats 里的 latest_fork_usec 很大(比如超过 1 秒),说明 fork 很慢------fork 期间主进程是阻塞的,这也是 Redis 延迟尖刺的常见来源。

条件 3:你连的是只读从节点(报错不同,但现象一样)

有一类「只能读不能写」不是 MISCONF,报错是:

text 复制代码
READONLY You can't write against a read only replica.

但它经常和上面那类混淆,因为现象完全一致:读正常、写失败。区分方法很简单:

bash 复制代码
redis-cli INFO replication
# role:slave  ← 说明你连的是从节点

常见成因:客户端连错了地址(主从切换后VIP 指向了从节点)、或者用了读写分离的客户端却把写请求发到了读连接上。这一类改 stop-writes-on-bgsave-error 完全没有用,必须换连接。

三、命令清单

命令 作用 看什么
redis-cli INFO persistence 持久化状态 ⭐ rdb_last_bgsave_status 是不是 err
`redis-cli INFO stats grep latest_fork_usec` fork 耗时
redis-cli CONFIG GET dir / dbfilename 落盘路径 路径是否存在、是否可写
df -h <dir> / df -i <dir> 磁盘空间 / inode 是否满了
ls -ld <dir> 目录权限 Redis 进程用户有没有写权限
tail -n 200 <logfile> 服务端日志 ⭐ 真正的原因只在这里
redis-cli CONFIG GET save 快照策略 是不是配了 save ""(关掉 RDB)
redis-cli CONFIG GET stop-writes-on-bgsave-error 保护开关 默认 yes
redis-cli BGSAVE 手动触发一次 用来验证修复是否成功
redis-cli LASTSAVE 最后成功时间 与当前时间对比,看多久没成功过了
redis-cli INFO replication 主从角色 排除「连到了从节点」
sysctl vm.overcommit_memory 内核参数 应该是 1
redis-cli CONFIG GET rename-command 命令重命名 有没有把 SAVE/BGSAVE 改掉(某些加固规范会这么做)

四、4 步恢复(第 2 步最容易被做错)

第 1 步:看日志,拿到真实原因。

bash 复制代码
tail -n 200 /var/log/redis/redis-server.log | grep -iE "error|fail|fork|save"

这一步不能跳。磁盘满、权限不足、fork 失败,三者的修复动作完全不同。凭猜改配置只会把问题盖住。

第 2 步:恢复写入(这是临时动作,不是修复)。

bash 复制代码
redis-cli CONFIG SET stop-writes-on-bgsave-error no

网上大量教程到此结束,但这句话的真实含义是:「Redis 你别再拦我了,数据能不能落盘我不在乎了」 。它是拆掉烟雾报警器,不是灭火。执行之后:

  • 写入立刻恢复 ✅
  • 但你的数据处于没有成功持久化的状态 ❌ ------ 此刻宕机,从最后一次成功的 RDB 之后的所有写入都会丢

所以第 2 步和第 3 步必须连着做,中间不能隔夜。

第 3 步:按第 1 步找到的原因修根因。

根因 修复动作
磁盘满 清理空间(优先清 dir 之外的东西:日志、core dump、旧备份),并加磁盘告警
目录不可写 chown redis:redis <dir>;确认不是只读挂载
dir 不存在 建目录并 CONFIG SET dir <path> + CONFIG REWRITE
fork 失败 sysctl vm.overcommit_memory=1;容器场景放宽内存限制
连的是从节点 改客户端连接地址,READONLY 与 MISCONF 是两回事
BGSAVE 被 rename 禁用 改回 rename-command 配置,或改用手动备份方式

第 4 步:验证,并把保护开关开回去。

bash 复制代码
redis-cli BGSAVE                                  # 手动跑一次
redis-cli INFO persistence | grep rdb_last_bgsave_status   # 应该是 ok
redis-cli LASTSAVE                                # 时间戳应该刚刚更新

# ⭐ 验证通过后,把保护重新打开
redis-cli CONFIG SET stop-writes-on-bgsave-error yes
redis-cli CONFIG REWRITE

只有 rdb_last_bgsave_status:ok 之后再把保护开回 yes,才是真正恢复了。

五、配置模板(防再犯)

conf 复制代码
# ===== 落盘路径:指向一个确定存在、确定可写、空间充足的目录 =====
dir /data/redis
dbfilename dump.rdb

# ===== 快照策略:按业务可容忍的丢失窗口来配 =====
save 900 1          # 15 分钟内至少 1 个 key 变化
save 300 10         # 5 分钟内至少 10 个 key 变化
save 60 10000       # 1 分钟内至少 10000 个 key 变化
# 缓存型 Redis 可以干脆关掉 RDB:save ""   (但要清楚这意味着重启即丢)

# ===== 保护开关:生产保持 yes =====
stop-writes-on-bgsave-error yes

# ===== AOF:对数据安全性要求高时开启 =====
appendonly yes
appendfsync everysec

# ===== 日志:出事时唯一的证据来源 =====
logfile /var/log/redis/redis-server.log
loglevel notice

配套的两条运维纪律:

  1. 磁盘使用率告警阈值设到 80%,不要等 95% 才告警------Redis 快照需要连续空间,等到快满时往往已经来不及;
  2. 监控 rdb_last_bgsave_status (不是只看写入是否可用)。它变成 err 就是明确的告警信号,比"接口报错"早得多。

六、总结

  1. MISCONF 不是故障,是保护机制生效了 :stop-writes-on-bgsave-error yes 在 BGSAVE 失败时主动拒写,防止你以为数据已落盘。读命令不受影响,所以现象是「能读不能写」。
  2. 3 类触发条件 :① 磁盘/权限/路径导致写不进去(最常见);② fork 失败(vm.overcommit_memory=0 或容器内存限制);③ 你连的是只读从节点(报错是 READONLY,与 MISCONF 无关,改保护开关没用)。
  3. CONFIG SET stop-writes-on-bgsave-error no 只是临时恢复手段,等于拆掉烟雾报警器。真正的修复是去服务端日志里找出 BGSAVE 失败的原因并处理掉。
  4. 恢复流程闭环 :看日志定位 → 临时放开写入 → 修根因 → 手动 BGSAVE 验证 rdb_last_bgsave_status:ok → 把保护开回 yes 并 CONFIG REWRITE。
  5. 防再犯靠两件事 :磁盘告警阈值设 80%,以及把 rdb_last_bgsave_status 纳入监控------它变 err 时接口还没开始报错,那才是最佳处理窗口。

本文基于 Redis 6/7 的行为;stop-writes-on-bgsave-error 的默认值、字段名在各版本一致,但 INFO persistence 的细分字段在不同版本略有差异,以你实际版本的输出为准。

相关推荐
workflower2 小时前
Agentic AI 时代的企业ICT 基础设施运维
运维·人工智能·机器学习·重构·云计算·汽车
szxinmai主板定制专家2 小时前
ZYNQ 数据采集方案踩坑总结:电力强电磁干扰、工业现场多协议适配优化
运维·人工智能·fpga开发·机器人·zynq
猫咪很可爱2 小时前
Oracle RAC 启动失败排查:私网通信与内核参数优化实战
运维·数据库·oracle
无足鸟ICT2 小时前
【RHCA+】RPM包验证和数字证书
linux·运维·服务器
我不会起名字3222 小时前
Redis 从 0 到 1(九):主库宕机 30 秒自动切主——1 主 2 从 3 哨兵 + Go 客户端
redis·docker·sentinel·高可用·哨兵
yunfengzhineng2 小时前
天津立体库货架配套厂家
大数据·运维·人工智能·python
星恒随风2 小时前
Linux线程概念详解:从分页存储、页表、MMU与TLB,深入理解线程和进程的区别与联系
linux·运维·笔记·学习·状态模式
救救孩子把2 小时前
AI面试八股文-13_12-AI 框架与运维面试题
运维·人工智能·面试
小范的技术工坊3 小时前
Linux应用开发(文件属性)
linux·运维·服务器