SpringBoot整合redis报错

  • 我的redis是直接通过docker官方镜像创建的, 用了1个多月了都没事,今天在登录网站首页的时候发现拿不到登录验证码了, 看看报错信息.

idea控制台报的错误

jakarta.servlet.http.HttpServlet.service(HttpServlet.java:537)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: io.lettuce.core.RedisReadOnlyException: READONLY You can't write against a read only replica.
	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:144)
	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:116)
	at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)
	at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111)
	at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:747)
	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:682)
	at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:599)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	... 1 more
org.springframework.data.redis.RedisSystemException: Error in execution
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:50)
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
	at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:40)
	at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:38)
	at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:248)
	at org.springframework.data.redis.connection.lettuce.LettuceConnection.await(LettuceConnection.java:961)
	at org.springframework.data.redis.connection.lettuce.LettuceConnection.lambda$doInvoke$4(LettuceConnection.java:818)
	at org.springframework.data.redis.connection.lettuce.LettuceInvoker$Synchronizer.invoke(LettuceInvoker.java:673)
	at org.springframework.data.redis.connection.lettuce.LettuceInvoker$DefaultSingleInvocationSpec.get(LettuceInvoker.java:589)
	at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.setEx(LettuceStringCommands.java:132)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.setEx(DefaultedRedisConnection.java:340)
	at org.springframework.data.redis.connection.DefaultStringRedisConnection.setEx(DefaultStringRedisConnection.java:795)
	at org.springframework.data.redis.core.DefaultValueOperations$8.potentiallyUsePsetEx(DefaultValueOperations.java:265)
	at org.springframework.data.redis.core.DefaultValueOperations$8.doInRedis(DefaultValueOperations.java:258)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:406)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:373)
	at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:97)
	at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:253)
	at com.chen.manager.service.impl.ValidateCodeServiceImpl.generateValidateCode(ValidateCodeServiceImpl.java:31)
	at com.chen.manager.controller.IndexController.generateValidateCode(IndexController.java:86)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:537)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: io.lettuce.core.RedisReadOnlyException: READONLY You can't write against a read only replica.
	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:144)
	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:116)
	at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)
	at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111)
	at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:747)
	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:682)
	at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:599)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)

错误分析

  • Spring Boot 应用在执行 Redis 写操作时遇到了 RedisReadOnlyException,提示 “READONLY You can’t write against a read only replica.”,意思是 当前 Redis 实例是只读的,从库(replica)无法执行写操作。

异常分析

1.	核心错误
Caused by: io.lettuce.core.RedisReadOnlyException: READONLY You can't write against a read only replica.

这是 Lettuce(Spring Boot 默认的 Redis 客户端)抛出的异常,表示应用试图在 只读的 Redis 副本(replica)上执行写入操作,但被拒绝。

2.	调用堆栈
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:253)
at com.chen.manager.service.impl.ValidateCodeServiceImpl.generateValidateCode(ValidateCodeServiceImpl.java:31)

generateValidateCode 方法尝试向 Redis 存入数据时失败了。


可能的原因
1. Redis 连接的是只读副本
• 你的 Spring Boot 连接的 Redis 实例可能是 只读从库,无法执行 SET 等写入操作。
• 如果你的 Redis 是主从模式(Master-Slave 或 Replica Set),并且 Spring Boot 连接到了 Slave(replica),写入请求就会失败。
2. Redis 主库(Master)失效或不可用
• 你的应用可能原本连接的是主库(Master),但主库宕机了,Redis 进入了 Failover(故障转移)模式,导致从库变成新的主库,而你的应用仍然连接的是原来的旧从库(现在变成了只读状态)。
3. Redis Sentinel 切换
• 如果你使用了 Redis Sentinel,它可能已经 将新的主库切换到别的节点,但应用的连接池还没有更新,导致它继续向旧的(现在是只读的)副本发送写入请求。
4. Spring Boot 配置错误
• 你的 Redis 配置可能错误地指定了一个 Replica,而不是 Master。

解决方案

方案 1:确保连接的是 Redis 主库

检查你的 Redis 连接配置,确保应用程序 连接的是 Redis 主库(Master),而不是副本:

spring:
  redis:
    host: <master-host>
    port: 6379

如果你是用 Redis 集群(Cluster),确保 spring.redis.cluster.nodes 指向正确的主节点。

方案 2:如果使用了 Sentinel,确保连接到正确的主库

如果你的 Redis 使用了 Sentinel,那么应该在 application.yml 配置:

spring:
  redis:
    sentinel:
      master: mymaster  # Sentinel 监控的主节点名称
      nodes: redis-sentinel-1:26379,redis-sentinel-2:26379,redis-sentinel-3:26379

这样,当 Redis 发生主从切换时,应用可以自动连接新的主库。

方案 3:检查 Redis 服务器状态

登录 Redis 服务器,执行以下命令:

redis-cli INFO replication

输出示例:

role:slave
master_host: 192.168.1.100
master_port: 6379

如果 role:slave,说明你连接的是 从库,需要改为主库的 IP 和端口。

方案 4:强制允许写入(不推荐)

如果你确定 Redis 只读模式不符合预期(比如误配置了从库),可以 手动在 Redis 上取消只读模式:

redis-cli

replicaof no one # 取消只读模式,让当前节点成为主库
CONFIG SET replica-read-only no

⚠️ 注意:这会导致主从模式失效,仅适用于本地调试!

方案 5:检查 Spring Boot 代码

如果你的代码支持读写分离,可以检查 RedisTemplate 或 StringRedisTemplate 的使用:

@Autowired
private RedisTemplate<String, String> redisTemplate;

public void saveData(String key, String value) {
redisTemplate.opsForValue().set(key, value); // 确保这里的 redisTemplate 连接的是主库
}

如果 redisTemplate 指向的是 从库,可以调整 RedisTemplate 配置,让它连接 Master。


总结

✅ 优先检查 Redis 服务器,确保你的应用连接的不是 只读副本(replica)。
✅ 如果 Redis 使用了主从模式或 Sentinel,应用应该连接 Master,而不是 Slave。
✅ 如果是 Spring Boot 代码问题,检查 RedisTemplate 是否正确配置为主库。

我的问题:进入redis容器看看日志

#进入容器
docker exec -it  redis  /bin/ bash
#查看日志
redis-cli INFO replication
#日志的信息
# Replication
role:slave
master_host:120.24.6.73
master_port:23334
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:0
slave_repl_offset:0
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:d8416d8bc9884a5845cb4833dc0d1dd4889b2ddf
master_replid2:a84853d316aeca2d1fbc329760e055688a3bf33a
master_repl_offset:0
second_repl_offset:1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
  1. Redis 处于只读模式
    slave_read_only:1 说明从节点默认是只读的,不能执行写操作。
    2. 主节点连接已断开
    master_link_status:down,表明从节点无法连接到主节点 120.24.6.73:23334,因此无法同步数据。
    3. 无主从同步进行
    master_last_io_seconds_ago:-1 和 master_sync_in_progress:0 说明已经长时间未同步数据。
    4. 没有可用的主节点
    connected_slaves:0 表明当前没有连接到其他 Redis 实例。

解决方案1

这里直接把自己升级为主节点

redis -cli
slaveof no one
  • 在重新连接就可以了 这么设置redis一重启就失效了, 还后还要反复操作. 可以直接采用下面配置文件的方式

解决方案2设置redis 永远不为从, 编辑你挂载的配置文件

  • 找到你挂载的配置文件
docker inspect redis

在这里插入图片描述

  • 修改配置文件 ,添加一句
    vim 配置文件名
    i 进入插入模式
    esc :wq 保存并退出
replicaof no one
  • 重启 redis 永久生效,就在也不会报这个错误了
docker restart redis-server