若依启动突然报 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 本身没有关系,也不是 captchaController 或 sysConfigServiceImpl 写错了。真正的故障点是 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 内存中的数据已经无法可靠地保存到磁盘,如果继续写入,可能会进一步扩大数据丢失的范围。
常见原因主要有以下几类:
- 服务器磁盘空间已经用完;
- Redis 数据目录没有写入权限;
- Redis 运行用户和数据目录所属用户不一致;
- 文件系统被挂载成只读;
- inode 耗尽,即使磁盘看似还有容量也无法创建新文件;
- Redis 所在容器的挂载目录异常;
- 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。先通过 find、du 和 ls -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 初始化失败,最终表现为若依无法启动。只有把异常链、服务配置和服务器资源状态串联起来看,才能真正找到根因,而不是一直在应用代码里绕圈子。