若依启动突然报 Redis MISCONF?一次从应用报错到磁盘爆满的完整排查记录

若依启动突然报 Redis MISCONF?一次从应用报错到磁盘爆满的完整排查记录

最近在启动一个若依后台项目时,遇到了一个看起来像 Spring Bean 注入失败的问题。项目使用的是 RuoYi 3.9.0、Spring Boot 2.5.15 和 Java 17,数据库连接正常,sys_config 表也能成功查询,但应用启动到一半便自动退出。

一开始日志里最显眼的是下面这段:

text 复制代码
Error creating bean with name 'captchaController':
Unsatisfied dependency expressed through field 'configService'

继续往下看,又能看到:

text 复制代码
Error creating bean with name 'sysConfigServiceImpl':
Invocation of init method failed

如果只盯着这两行,很容易把排查方向放在 Spring Bean 注入、配置类加载或者若依源码上。但真正有用的信息其实藏在异常链的最底部:

text 复制代码
RedisCommandExecutionException:
MISCONF Redis is configured to save RDB snapshots,
but is currently not able to persist on disk.
Commands that may modify the data set are disabled.

这次问题和 Spring 本身没有关系,也不是 captchaControllersysConfigServiceImpl 写错了。真正的故障点是 Redis:它尝试生成 RDB 快照时写入磁盘失败,触发了写保护,最终拒绝了若依启动阶段的缓存写入。

一、为什么 Redis 出问题,会导致若依整个项目启动失败

若依启动时,SysConfigServiceImpl 会执行初始化方法,从数据库读取系统配置,并将部分配置写入 Redis 缓存。

从日志可以看到,数据库查询其实已经执行成功:

text 复制代码
select config_id, config_name, config_key, config_value,
config_type, create_by, create_time, update_by,
update_time, remark
from sys_config

查询结果也没有问题:

text 复制代码
Total: 8

真正失败的是后面的 Redis 写入操作:

text 复制代码
RedisCache.setCacheObject
SysConfigServiceImpl.loadingConfigCache
SysConfigServiceImpl.init

也就是说,项目启动过程大致如下:

text 复制代码
启动 Spring Boot
→ 初始化数据源
→ 查询 sys_config
→ 将系统配置写入 Redis
→ Redis 拒绝写入
→ SysConfigServiceImpl 初始化失败
→ captchaController 依赖注入失败
→ Spring 容器启动失败

所以日志表面上看是控制器依赖注入失败,实际上是 Redis 写保护引发的连锁反应。

排查 Spring Boot 启动异常时,不能只看最上面的 UnsatisfiedDependencyException,而应该沿着 Caused by 一直往下看。异常链最底层的信息,通常才是根因。

二、Redis 为什么会进入 MISCONF 状态

Redis 默认支持 RDB 快照持久化。当配置的条件被满足时,Redis 会执行后台保存,把内存中的数据写入磁盘。

如果保存失败,并且配置中启用了:

conf 复制代码
stop-writes-on-bgsave-error yes

Redis 就会停止接收可能修改数据的命令,例如:

text 复制代码
SET
HSET
LPUSH
INCR
DEL

此时读取通常还能正常执行,但所有写入操作会返回 MISCONF

这个保护机制本身没有问题。它是在提醒管理员:Redis 内存中的数据已经无法可靠地保存到磁盘,如果继续写入,可能会进一步扩大数据丢失的范围。

常见原因主要有以下几类:

  1. 服务器磁盘空间已经用完;
  2. Redis 数据目录没有写入权限;
  3. Redis 运行用户和数据目录所属用户不一致;
  4. 文件系统被挂载成只读;
  5. inode 耗尽,即使磁盘看似还有容量也无法创建新文件;
  6. Redis 所在容器的挂载目录异常;
  7. RDB 临时文件创建或重命名失败。

在这次排查中,最后确认是最常见也最直接的一种:系统盘已经完全占满。

三、第一次处理为什么没有效果

看到 MISCONF 后,我最先想到的是临时关闭 Redis 的持久化和写保护:

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

从命令本身来看没有问题。如果 Redis 只是作为缓存使用,而且能够接受服务重启后缓存丢失,这样处理可以快速恢复写入。

但重新启动若依后,日志里仍然出现完全相同的 MISCONF

问题出在一个很容易忽略的地方:刚才修改的是当前服务器上的本地 Redis,而若依连接的根本不是本机 Redis。

为了确认项目实际使用的 Redis,可以直接查看 Jar 包中的配置:

bash 复制代码
unzip -p ruoyi-admin.jar BOOT-INF/classes/application.yml \
| grep -A 12 -i redis

查到的配置类似:

yaml 复制代码
spring:
  redis:
    host: 43.143.48.239
    port: 6379
    database: 1
    password: ********

这说明若依连接的是另一台远程服务器上的 Redis。因此,之前在应用服务器执行的:

bash 复制代码
redis-cli CONFIG SET ...

默认连接的是 127.0.0.1:6379,和若依实际使用的 Redis 根本不是同一个实例。

这也解释了为什么命令看似执行成功,应用仍然报错。

遇到类似问题时,不能仅凭"服务器上装了 Redis"就默认应用连接的是本机。至少要确认以下四项:

text 复制代码
host
port
database
password

其中,CONFIG SET 修改的是整个 Redis 实例的配置,与数据库索引无关;但测试业务写入时,最好切换到应用实际使用的数据库,例如本次若依使用的是数据库 1:

bash 复制代码
redis-cli -h Redis地址 -p 6379 -a '密码' -n 1 SET ruoyi_test 123

只要这条命令仍然返回 MISCONF,若依就一定无法正常启动。

四、登录远程 Redis 服务器后,真正根因终于暴露

登录 Redis 所在服务器后,先查看磁盘空间:

bash 复制代码
df -h

结果如下:

text 复制代码
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       266G  255G     0 100% /

系统盘总容量约为 266GB,已经使用255GB,可用空间显示为0,利用率达到100%。

此时问题已经很清楚了:Redis 在执行 RDB 快照时,需要在数据目录创建临时文件,再完成落盘和文件替换。但系统盘没有任何可用空间,RDB 保存自然会失败。

继续检查 Redis 的运行方式:

bash 复制代码
docker ps

没有发现 Redis 容器,再查看系统服务:

bash 复制代码
systemctl status redis --no-pager

结果显示:

text 复制代码
redis.service - Redis persistent key-value database
Active: active (running)
Main PID: 1827
/usr/bin/redis-server 0.0.0.0:6379

由此确认,Redis 是通过 systemd 直接运行的,并不是 Docker 容器。

所以,这次故障的完整因果关系是:

text 复制代码
系统盘100%占满
→ Redis无法生成RDB快照
→ 最近一次后台保存失败
→ stop-writes-on-bgsave-error触发
→ Redis拒绝SET等写命令
→ 若依无法初始化配置缓存
→ Spring Bean初始化失败
→ 整个应用启动失败

五、先恢复业务,再处理磁盘

如果 Redis 只承担缓存、验证码、登录状态等用途,并不保存不可丢失的核心业务数据,可以先关闭写保护和持久化,让业务尽快恢复。

在 Redis 服务器本机执行:

bash 复制代码
redis-cli -a 'Redis密码' CONFIG SET stop-writes-on-bgsave-error no
redis-cli -a 'Redis密码' CONFIG SET save ""
redis-cli -a 'Redis密码' CONFIG SET appendonly no

然后切换到若依使用的数据库1,验证写入:

bash 复制代码
redis-cli -a 'Redis密码' -n 1 SET ruoyi_test 123
redis-cli -a 'Redis密码' -n 1 GET ruoyi_test

预期结果为:

text 复制代码
OK
123

为了避免密码直接保存在 Shell 历史记录里,也可以采用环境变量方式:

bash 复制代码
read -s -p "Redis密码: " REDIS_PASS
echo
export REDISCLI_AUTH="$REDIS_PASS"

redis-cli CONFIG SET stop-writes-on-bgsave-error no
redis-cli CONFIG SET save ""
redis-cli CONFIG SET appendonly no
redis-cli -n 1 SET ruoyi_test 123
redis-cli -n 1 GET ruoyi_test

unset REDISCLI_AUTH
unset REDIS_PASS

确认 Redis 可以正常执行 SET 后,再回到应用服务器启动若依:

bash 复制代码
cd /data/book
nohup java -jar ruoyi-admin.jar > app.log 2>&1 &
tail -200f app.log

这里要注意,CONFIG SET 修改的是当前运行实例的动态配置。如果 Redis 重启,配置可能恢复。因此,这种处理更适合用于紧急恢复,后续还要根据实际需求修改配置文件。

六、快速查找系统中超过100MB的日志

虽然临时关闭写保护能够恢复 Redis,但系统盘100%才是必须解决的根因。如果不清理磁盘,MySQL、Nginx、Java 应用甚至操作系统本身都可能继续出现异常。

可以先查看整个根分区中超过100MB的 .log 文件:

bash 复制代码
find / -xdev -type f -name "*.log" -size +100M \
-exec ls -lh {} \; 2>/dev/null

其中:

  • / 表示从根目录开始搜索;
  • -xdev 表示不跨越其他挂载点;
  • -type f 表示只查找普通文件;
  • -name "*.log" 表示只匹配 .log 文件;
  • -size +100M 表示文件大于100MB;
  • 2>/dev/null 用于隐藏无权限访问等无关报错。

如果希望按照大小从大到小排列,可以使用:

bash 复制代码
find / -xdev -type f -name "*.log" -size +100M \
-printf '%s %p\n' 2>/dev/null \
| sort -nr \
| awk '{printf "%.2f GB\t%s\n", $1/1024/1024/1024, $2}'

很多业务日志并不一定以 .log 结尾,还可能是 nohup.out、轮转日志或者压缩文件,因此可以扩大查询范围:

bash 复制代码
find / -xdev -type f \
\( -name "*.log" -o -name "nohup.out" -o -name "*.log.*" \) \
-size +100M -exec ls -lh {} \; 2>/dev/null

如果只是想快速判断哪个一级目录占用最多,可以执行:

bash 复制代码
du -xhd1 / 2>/dev/null | sort -h
du -xhd1 /var 2>/dev/null | sort -h
du -xhd1 /data 2>/dev/null | sort -h

一般来说,服务器磁盘被占满,常见位置包括:

text 复制代码
/var/log
/data/logs
/data/项目目录/logs
/opt/应用目录/logs
Docker数据目录
数据库备份目录
上传文件目录
nohup.out

七、正确清理日志,不要直接删除正在写入的文件

找到明确不需要保留的大日志后,推荐使用 truncate 清空:

bash 复制代码
truncate -s 0 /具体路径/application.log

相比直接执行:

bash 复制代码
rm -f application.log

truncate 更适合处理正在被 Java、Nginx 或其他进程持续写入的日志。它会保留原来的文件、权限和文件描述符,只把内容清空,应用通常不需要重启。

常见系统日志也可以按需清空:

bash 复制代码
truncate -s 0 /var/log/messages
truncate -s 0 /var/log/secure
truncate -s 0 /var/log/cron
truncate -s 0 /var/log/maillog

Systemd 日志可以限制到100MB:

bash 复制代码
journalctl --vacuum-size=100M

清理 YUM 缓存:

bash 复制代码
yum clean all
rm -rf /var/cache/yum/*

清理完成后再次查看:

bash 复制代码
df -h

如果已经删除或清空了大日志,但磁盘空间仍然没有释放,需要检查"文件已删除,但仍被进程占用"的情况:

bash 复制代码
lsof +L1

Linux 中,进程打开一个文件后,即使文件被删除,只要文件描述符没有关闭,磁盘空间仍然不会真正释放。这时需要重启占用该文件的具体服务,而不是继续反复删除文件。

八、是否应该彻底关闭 Redis 持久化

能不能关闭 Redis 持久化,取决于 Redis 中保存的是什么。

如果 Redis 只用于以下内容:

text 复制代码
验证码
用户登录Token
菜单和系统配置缓存
临时接口数据
短期分布式锁
可以从数据库重新构建的数据

那么关闭持久化通常可以接受。Redis 重启后,用户可能需要重新登录,缓存会重新加载,但 MySQL 中的核心业务数据不会丢失。

可以在 Redis 配置文件中设置:

conf 复制代码
save ""
appendonly no
stop-writes-on-bgsave-error no

修改前建议备份:

bash 复制代码
cp /etc/redis.conf /etc/redis.conf.bak
vi /etc/redis.conf

然后重启 Redis:

bash 复制代码
systemctl restart redis
systemctl status redis --no-pager

重启后验证:

bash 复制代码
redis-cli -a 'Redis密码' CONFIG GET save
redis-cli -a 'Redis密码' CONFIG GET appendonly
redis-cli -a 'Redis密码' -n 1 SET ruoyi_test 456

但是,如果 Redis 中保存了无法从其他地方恢复的重要数据,例如订单状态、可靠队列、唯一计数、未落库任务等,就不能简单关闭持久化。此时应该释放磁盘空间、修复目录权限,并恢复正常的 RDB 或 AOF 策略。

九、这次故障给我的几个提醒

这次问题看起来只是一个 Redis 报错,但排查过程中暴露了几个很典型的运维误区。

第一,不要被最外层异常带偏。captchaController 注入失败只是结果,沿着 Caused by 继续往下看,才能找到 Redis 的 MISCONF

第二,执行修复命令前必须确认目标实例。应用可能连接本机 Redis、远程 Redis、Docker Redis、云 Redis或集群节点。修改错实例,命令执行得再正确也没有意义。

第三,遇到 Redis 持久化失败,不要只想着关闭持久化。临时关闭写保护可以快速恢复业务,但磁盘100%意味着整台服务器都处于危险状态,必须尽快清理。

第四,不要无差别执行 rm -rf。先通过 findduls -lh 找到真正的大文件,再对确认无用的日志进行清理。对于正在写入的日志,优先使用 truncate

第五,生产环境必须配置日志轮转和磁盘告警。等到系统盘达到100%后再处理,往往已经影响业务。至少应该在磁盘使用率达到80%时告警,达到90%时升级告警。

十、后续建议:避免同类故障再次发生

这次服务恢复后,还需要补上长期治理措施。

首先,为 Java 项目配置日志滚动策略,限制单个文件大小和保留天数。例如 Logback 可以设置单个日志文件最大100MB,只保留7天或30天,并限制日志总容量。

其次,检查是否存在多个项目长期把日志写入 nohup.out。使用下面这种启动方式时:

bash 复制代码
nohup java -jar app.jar &

如果没有显式重定向,标准输出会不断写入 nohup.out,几年下来可能达到几十GB甚至上百GB。更合理的方式是:

bash 复制代码
nohup java -jar app.jar > /data/logs/app.log 2>&1 &

同时配合日志轮转。

再次,配置磁盘监控。即使没有完整的 Prometheus 和 Grafana,也可以使用定时脚本检测磁盘使用率。超过阈值后发送邮件、企业微信或飞书通知。

最后,Redis 不应直接暴露在公网。此次配置中使用了公网IP和密码连接 Redis,这种方式存在较大的安全风险。更稳妥的方案是使用内网地址、安全组白名单、VPN或者 SSH 隧道,并及时更换已经暴露过的密码。

总结

这次若依启动失败,表面现象是 Spring Bean 注入异常,实际原因是远程 Redis 无法完成 RDB 持久化。第一次修复没有效果,是因为修改了本机 Redis,而应用实际连接的是另一台远程服务器。登录远程服务器后,通过 df -h 最终确认系统盘已经100%占满。

整个问题的正确处理顺序是:

text 复制代码
查看完整异常链
→ 找到 Redis MISCONF
→ 确认应用实际连接的 Redis
→ 测试目标实例是否能够写入
→ 登录 Redis 服务器检查磁盘
→ 临时关闭写保护恢复业务
→ 查找并清理大日志
→ 验证磁盘和 Redis
→ 完善持久化、日志轮转与磁盘告警

很多线上故障并不是某一个组件突然"坏了",而是底层资源问题沿着依赖链不断向上传导。磁盘满导致 Redis 持久化失败,Redis 写保护又导致 Spring 初始化失败,最终表现为若依无法启动。只有把异常链、服务配置和服务器资源状态串联起来看,才能真正找到根因,而不是一直在应用代码里绕圈子。

相关推荐
boooooooom2 小时前
尝尝咸淡:从朴素 RAG 到 Graph RAG——一个烹饪问答系统的三层检索升级之路
前端·后端·llm
2601_963870183 小时前
【计算机毕业设计】基于Vue+Spring Boot的助农电商系统的设计与实现
spring boot·后端·课程设计
sun༒3 小时前
Spring @Scheduled 定时任务详解:Cron表达式、执行顺序、优先级与并行调度
java·后端·spring
yuhaiqiang3 小时前
从这两件事就能看出 vibecoding 距离专业作品差距有多大?AI 能抹平技术,但抹不平品味 !
前端·后端·程序员
野生技术架构师4 小时前
Spring Boot 实现数据脱敏:自定义注解 + Jackson 序列化器
java·spring boot·后端
明月_清风5 小时前
vLLM 深度实战:2026 年生产级 LLM 推理引擎完全指南
前端·后端·ai编程
吃饱了得干活6 小时前
限界上下文之后:微服务怎么拆、上下文怎么聊?
java·后端·架构
星火10246 小时前
【Groovy翻译-进阶篇】运行时元编程与编译时元编程(上)
后端·groovy