做后端开发的人,几乎都绕不开Redis,尤其在Spring Boot框架下,缓存、分布式锁、会话共享、限流、排行榜、消息队列,很多功能都会优先考虑用Redis来承接。于是网络上到处能看到“Spring Boot整合Redis”的教程,但大部分教程只演示默认单机模式下的配置,后面说到主从、哨兵、集群,就含糊带过或者直接不写了。
先说个我经常看到的场景:项目初期Redis单机跑得好好的,上线后流量一上来就频繁报警,或者某次服务器重启后缓存全部丢失,因为Redis进程挂了没人恢复。这时候才想起来要搭主从复制、用哨兵做自动切换,或者在多个节点之间做集群分片。但问题在于,单机、主从、哨兵、集群这四种模式不只是部署层面的差异,Spring Boot里的配置方式、连接参数、故障处理机制也完全不同,比如Spring Boot 3.x用了新的spring.data.redis前缀,和旧版的spring.redis前缀不一样,直接照搬网上老教程就会踩坑。
这篇文章把四种模式一次性讲清楚。我会先从它们各自的原理说起,再逐个给出Spring Boot下的配置文件和连接工厂配置,最后整理一份高频故障排查清单。文中所有配置都基于实际可运行的做法,我踩过的坑也都会点出来,希望看完你能在自己项目里直接照着改。
1. Redis四种模式:先分清楚它们本质差别在哪
很多人把主从、哨兵、集群混为一谈,其实它们是不同维度上的方案。主从解决的是数据冗余和读写能力问题,哨兵解决的是主节点故障时的自动切换问题,集群解决的则是数据容量和横向扩展问题。单机则是一切的基础,也是上手最快的模式。
1.1 单机模式:最简单但也最容易被忽略风险
单机模式就是一个Redis进程对外提供服务,所有数据都存在这一台机器的内存和持久化文件里。你本地安装redis-server后,默认端口6379跑起来的就是单机模式。
这种模式的优势非常明显:部署成本低、运维简单、没有节点间通信开销、数据一致性天然保证。开发调试阶段用它完全没有问题。缺点同样扎眼:一台机器挂了,Redis直接不可用;数据量增长到单机内存上限,扩容方案有限;写请求和读请求都压在同一个节点上,并发瓶颈很快到来。
在Spring Boot里配置单机模式也是最省心的。只需要指定host、port、password,再把database选好,一个RedisTemplate就能直接注入使用。但要注意,单机模式通常只适合以下场景:开发环境、预发环境、业务量比较小的生产环境、或者已经有其他高可用方案兜底的服务。
1.2 主从复制:把数据安全感和读写能力提上去
主从复制解决的是单点数据和单点故障问题。一个主节点,若干个从节点,主节点负责接收写命令,从节点通过复制机制同步主节点数据,同时可以承担读请求。Redis里复制数据的过程大致是:从节点向主节点发送PSYNC命令,主节点fork出子进程生成RDB快照发给从节点,期间产生的增量写命令会缓存在复制积压缓冲区里,之后继续通过命令流同步给从节点。
在主从模式下,主节点仍然只有一台,一旦主节点宕机,如果没有额外的故障转移机制,从节点不会自动上位,写服务就中断了。这是主从模式和哨兵模式最核心的区别。所以主从模式适合做数据备份和读写分离扩展,高可用还得靠哨兵来补。
Spring Boot直接配置主从复制其实没什么特别之处,因为它只是把连接信息指向其中一个节点。如果你想在应用层做读写分离,得给主节点和从节点各配一个连接工厂,再分别封装写操作和读操作的RedisTemplate。我一般不建议新手一开始就自己封装读写分离,先确保主从同步稳定,再慢慢优化。
1.3 哨兵模式:在主从复制之上增加自动切换能力
哨兵模式的核心是建一组哨兵进程,专门用来监控Redis主从节点的运行状态。哨兵会定期向所有节点发送心跳命令,某个主节点被判断为客观下线后,哨兵集群会投票选出一个从节点晋升为新主节点,并通知客户端更新连接信息。
哨兵通常部署奇数个节点(比如3个或5个),内部通过类似Raft的一致性协议做领导者选举,避免脑裂场景。哨兵模式下整个Redis系统对外仍然表现为一个主节点地址,客户端连接到的实际上是哨兵,然后由哨兵告诉客户端当前真正的master是谁。
Spring Boot对哨兵的支持是原生集成的,配置上要指定master名称和哨兵节点列表。这样配置之后,当主节点发生故障切换,Spring Data Redis会重新从哨兵获取主节点信息,客户端感知到的是短暂的连接中断,但不需要人工改配置。
1.4 集群模式:把数据分片到多台机器上
Redis Cluster在Redis 3.0版本正式引入,它把整个数据空间划分成16384个哈希槽,每个节点负责其中一部分槽位。写入某个key时,Redis会根据CRC16算法算出key属于哪个槽,再由该槽所在的节点提供服务。节点数量可以通过水平扩容来增加,数据也能在多个主节点之间均衡分布。
集群模式解决了单机内存上限和大并发写入的问题,也具备一定的容错能力:每个主节点都可以挂若干从节点,如果某个主节点挂了,它的从节点会接替它继续服务。但集群带来的限制也很现实:多key操作只有在所有key的哈希槽一致时才能执行,比如事务、Lua脚本、跨key的rename操作都会受限。
Spring Boot配置集群模式同样不复杂,把集群所有节点地址填进去,Spring Data Redis会自动完成槽位探测、节点发现和请求路由。但应用层代码需要遵守集群约束,否则线上会出现MOVED、CROSSSLOT这类错误。
四种模式的关键差异我用下面这个表格再压缩一遍,方便你和业务需求对照。
| 模式 | 数据容量 | 高可用能力 | 读写扩展 | 运维复杂度 |
|---|---|---|---|---|
| 单机 | 单节点内存 | 无,节点宕机即不可用 | 有限,单节点承载 | 最低 |
| 主从复制 | 受限于主节点,从节点冗余数据 | 主节点故障需人工切换 | 从节点可以扩展读 | 较低 |
| 哨兵模式 | 受限于主节点 | 自动故障切换,高可用 | 仍依赖主节点写,可扩展读 | 中等 |
| 集群模式 | 多主节点水平扩展 | 主节点由从节点自动接管 | 读写都可以分散多节点 | 较高 |
2. Spring Boot集成Redis的前置准备:依赖、序列化与环境
不管最终用哪种模式,Spring Boot集成Redis的基础工作是一样的。先要把依赖加对、序列化策略想清楚、连接工厂选好,再往上搭建各种模式,不然后面配置集群或哨兵时容易把错误来源搞混。
2.1 引入依赖时,注意Spring Boot版本带来的配置前缀变化
在Spring Boot项目中接入Redis,常规做法是添加spring-boot-starter-data-redis依赖。这个起步依赖会把Spring Data Redis、连接工厂、RedisTemplate等核心组件全部引入进来。
如果你用的是Maven,以前的项目一般这么写:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.7.18</version> </dependency>如果是Spring Boot 3.x,起步依赖坐标不变,但默认会和Java 17、Jakarta EE体系绑定。需要注意的配置项前缀变化是:Spring Boot 2.x使用spring.redis开头的配置,Spring Boot 3.0以后改成了spring.data.redis开头。这两种前缀不通用。网上很多教程没有注明版本,直接把旧前缀配置复制到新工程里,会发现Redis相关的自动配置似乎没生效。
依赖引入时不需要手动添加Lettuce或Jedis库,Spring Boot起步依赖里已经带上了默认客户端。但如果要显式切换客户端,就得自己排除默认依赖再引入另一个客户端,这一点后面细说。
2.2 序列化策略不规划好,后面全是乱码事
Redis本身只存储字节数据,不关心你存进去的Java对象是JSON还是JDK序列化的产物。Spring Data Redis默认用JdkSerializationRedisSerializer做对象的序列化,这对熟悉原理的人来说问题不大,但对业务系统而言非常不友好。最直接的表现是:你用Redis Desktop Manager看缓存里的数据,看到的是一长串以AC ED 00 05开头的二进制乱码,Key也不直观,难以排查线上问题。
我的做法是给RedisTemplate统一指定key和value的序列化方式。key使用StringRedisSerializer,value使用GenericJackson2JsonRedisSerializer。这样存进去的对象自动转成JSON,可读性大大提升,绝大多数项目都能直接用。
配置代码也不要写得太花哨,够用即可:
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); StringRedisSerializer stringSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } }注意GenericJackson2JsonRedisSerializer在序列化时会写入对象类型信息,反序列化时才能恢复成正确的Java对象。如果业务里对JSON格式有洁癖,也可以自己定义ObjectMapper,但普通项目里用默认的就够了。
2.3 连接客户端选型:Lettuce还是Jedis
Spring Boot默认Lettuce,它对Redis Cluster和哨兵模式的支持也更完善。Lettuce本身基于Netty,使用单连接多路复用的机制,在大多数并发场景下节省资源。Jedis更偏向传统阻塞IO模型,每申请一个连接都需要走网络握手,高并发环境下更容易出现连接数不足的问题。
除非是团队里有人特别熟悉Jedis,或者历史项目一直用Jedis,否则我建议直接用Lettuce。后续配置池化参数时,Lettuce和Jedis的配置前缀有差异,这个我会在单机模式章节给出一份完整的配置。
3. 单机模式配置:从最简起步到连接池调优
单机模式是其他所有模式的基础。先把这一套配置跑通,再谈主从、哨兵、集群,思路会清晰很多。
3.1 一份可以直接使用的Spring Boot单机配置
在application.yml中,如果用的是Spring Boot 3.x,默认配置如下:
spring: data: redis: host: 127.0.0.1 port: 6379 database: 0 password: yourpassword timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 0 max-wait: 3s如果你的项目还在Spring Boot 2.x上,把前缀改成spring.redis即可,其他字段含义一致。注意database这个参数,很多教程喜欢在这里写个1或者2来区分业务缓存,实际使用中Redis默认有16个数据库,线上我基本只用0号库。如果确实要隔离,我更推荐直接用不同key前缀,或者拆Redis实例,因为集群模式下database 0以外的库并不支持。
password字段如果没有设置,就注释掉或者留空。如果开启了Redis的protected-mode并设置了密码,这里不填,启动时会直接报NOAUTH Authentication required。
timeout字段是连接超时时间,线上一般设3秒到5秒。如果Redis响应很慢,设得太短会导致大量请求提前超时,设得太长又会让线程长时间卡住,要根据业务接口的耗时来决定。
3.2 Lettuce连接池参数的直观理解
连接池参数看起来不多,但含义容易混淆。max-active代表连接池中最多可以有多少个活跃连接,好比一条高速公路的收费站最多同时放行多少辆车。max-idle是最多保留多少个空闲连接,空闲连接过多占用内存,过少又可能在突发流量时来不及创建新连接。max-wait是当连接池资源耗尽时,新请求最多等待多长时间再报错,等待时间设成3秒,意味着超过3秒还拿不到连接就会抛出连接池耗尽异常。
在单机模式下,我建议起步阶段用一组保守参数:max-active不超过CPU核数的两倍;max-idle保持和min-idle一致,可以避免频繁创建销毁连接。等到压测时再根据实际线程数调整,而不是一上来就调大,否则连接池反而变成新的瓶颈。
3.3 快速验证单机配置是否生效
配置写完以后,最简单的验证方式就是在启动类里临时注入RedisTemplate,然后在ApplicationRunner里执行一次set操作:
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component public class RedisConnectChecker implements ApplicationRunner { private final RedisTemplate<String, Object> redisTemplate; public RedisConnectChecker(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void run(ApplicationArguments args) { String key = "boot:connect:test"; redisTemplate.opsForValue().set(key, "ok"); Object value = redisTemplate.opsForValue().get(key); System.out.println("Redis connect test result: " + value); } }看到控制台输出ok的字符串,说明连接和序列化都正常。如果看到拒绝连接或者反序列化异常,那就反过来检查host、port、password和database序号。这一步最考验耐性,但值得先跑通。
4. 主从复制与哨兵模式配置:高可用才是核心目标
很多人跑通单机模式后,马上就想上哨兵。这里我要提醒一句:哨兵模式的前提是主从复制已经稳定工作,所以在配置Spring Boot之前,至少先搭建好一主一从或者一主多从的Redis节点。如果连主从同步都做不好,哨兵切换再顺,数据完整性也没法保证。
4.1 先把主从复制搭建起来
搭建主从复制本身不复杂。假设我已经有一台主节点运行在127.0.0.1:6379,现在要加一个从节点运行在127.0.0.1:6380。只需要在第二台Redis的配置文件里加一行:
replicaof 127.0.0.1 6379如果是老版本Redis,可能会看到slaveof这个写法,效果一样。修改完配置文件后启动实例,在主节点上执行info replication,就能看到从节点已经接入。此时向主节点写入的数据,会自动同步到从节点。
主从模式下有个容易误判的点:如果Redis实例开启了密码,复制链路上也需要配置相同的密码,否则主从同步会反复失败。通常方式是给主节点和从节点设置同样的requirepass,同时在从节点的配置文件里配置masterauth,填主节点的密码。
4.2 应用层如何对接主从架构
Spring Boot原生的Redis配置对象只能填写一个standalone host和port,所以主从架构下最直接的做法还是连接主节点,让写请求和读请求都走主节点。从节点更多的意义是热备份,以及当主节点故障时可以被哨兵提升为新的主节点。
如果业务确实要求读请求分散到从节点,从而缓解主节点的读压力,就需要自己实现路由。常见做法是定义两个连接工厂,一个指向主节点,一个指向从节点,然后封装一个自定义的RedisTemplate或者工具类,读操作走从节点的template,写操作走主节点的template。
这里不推荐自己用代码判断当前节点是不是master,然后动态切换。主从模式下从节点挂掉一样会有部分读失败,你要有自己的降级策略,比如读请求临时切回主节点。
4.3 哨兵模式在Spring Boot中的标准配置
哨兵模式的搭建需要至少三个Sentinel进程,分别跑在26379端口上。配置Spring Boot时,不需要填写具体的master地址,而是告诉它哨兵节点在哪、master在哨兵里叫什么名字。
在Spring Boot 3.x的配置文件中:
spring: data: redis: password: yourpassword sentinel: master: mymaster nodes: - 127.0.0.1:26379 - 127.0.0.1:26380 - 127.0.0.1:26381在Spring Boot 2.x中,把spring.data.redis改为spring.redis即可。这里的password是哨兵监控的Redis节点密码,一般指Redis实例自身的requirepass。如果哨兵节点本身也配置了密码,Spring Data Redis也支持哨兵密码配置,但Spring Boot的application.yml里不一定有直接对应的属性,很多时候需要走Java配置类。
如果想用Lettuce的自动故障转移能力,还有一种灵活的Java配置方式:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; @Configuration public class SentinelConfig { @Bean public LettuceConnectionFactory lettuceConnectionFactory() { RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster") .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380) .sentinel("127.0.0.1", 26381); sentinelConfig.setPassword(RedisPassword.of("yourpassword")); return new LettuceConnectionFactory(sentinelConfig); } }这种配置方式能更精确地控制Sentinel密码、主节点密码等细节,也更适合在Spring Boot自动配置不满意的时候介入。
验证哨兵配置是否生效,可以这么做:先人工方式停掉当前master节点,观察哨兵日志是否完成主从切换。切换完成后,再检查Spring Boot应用日志,正常情况下应该能看到连接被关闭,随后自动恢复并连接到新的master。如果应用侧迟迟不恢复,那就涉及Lettuce的拓扑刷新,具体排查我在第六章写清楚。
5. 集群模式配置:多节点水平扩展的正确打开方式
集群模式和前面几种模式有很大不同。集群模式下客户端需要知道每个key的哈希槽分布在哪个节点上,一开始连上任意一个节点后,客户端会自动学习整个集群的slot分布,后续请求直接路由到正确的节点。如果集群扩容或者节点故障导致slot重新分配,客户端需要对MOVED和ASK错误进行处理。
5.1 搭建Redis Cluster的最低要求
生产环境至少3个主节点、3个从节点,即6个Redis进程。每个主节点分布一部分哈希槽,每个从节点作为对应主节点的备份。用docker-compose搭建时,通常会为每个节点准备独立的data目录和redis.conf。为了演示Spring Boot配置,我把最关键的cluster相关参数列出来:
port 7001 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 appendonly yes protected-mode nocluster-enabled yes是开启集群功能最核心的一项。关掉protected-mode是为了让集群节点之间能完成通信。节点全部启动后,需要通过redis-cli把节点聚合成集群,例如:
redis-cli --cluster create \ 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \ 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \ --cluster-replicas 1如果一切顺利,终端会提示16384个槽已经全部分配完毕,每个主节点都接上了至少一个从节点。
5.2 Spring Boot中配置cluster节点列表
Spring Boot里配置集群,并不需要把6个节点全写上去,但为了可用性,最好把主节点和从节点都填上。客户端启动时会主动感知集群拓扑,后续会自动处理节点间路由。
在Spring Boot 3.x中,配置如下:
spring: data: redis: password: yourpassword cluster: nodes: - 127.0.0.1:7001 - 127.0.0.1:7002 - 127.0.0.1:7003 - 127.0.0.1:7004 - 127.0.0.1:7005 - 127.0.0.1:7006 max-redirects: 3 lettuce: cluster: refresh: adaptive: true period: 30smax-redirects表示一次操作请求最多跟随多少次MOVED重定向。配置太大会掩盖路由问题,太小又可能在节点迁移过程中造成不必要的失败,我一般设3到5。
又到了集群使用必须要面对的限制:多key操作。在Redis Cluster中,只有所有key的哈希槽一致时才能执行多个key的原子操作。如果你硬要执行MSET或Lua脚本,Redis会返回CROSSSLOT错误。解决办法是使用哈希标签,也就是给key加一个带花括号的公共子串,让Redis按照花括号里的内容计算哈希槽。例如把业务id作为标签,让相关key都落到同一个槽位。
5.3 集群模式下的模板使用经验
Spring Data Redis针对集群这套逻辑已经封装得比较好。你注入RedisTemplate之后,底层会自动处理MOVED、ASK重定向,普通业务代码基本感受不到集群的存在。
但要注意几个容易踩坑的地方。事务支持在集群模式下非常受限,MULTI/EXEC事务只能在同一槽位的多个key上执行,跨槽位就报错。Lua脚本同样需要脚本里涉及的key都在同一个槽位。管道操作在集群中也比单机复杂得多,不是简单把所有命令塞到同一个管道就能保证性能。
用集群做分布式锁时要格外小心。RedissonFairLock、RedLock这些分布式锁方案在集群模式下的可用性,需要结合业务对一致性的要求来判断;常规的单节点SETNX锁在故障转移瞬间可能会出现锁丢失,这是Redis集群本身的AP属性决定的,不要因为代码能跑通就忽视这个风险。
6. 配置后的故障排查与避坑实录
这一章全部来自我在真实项目里验证过的排查思路。你会发现很多故障不是Redis本身的问题,而是配置和应用层理解不到位造成的。
6.1 无法连接Redis:先分清是哪一层断了
最常见的报错是Unable to connect to Redis,后面跟着ConnectException: Connection refused或Connection timed out。遇到这种错误,先别急着看Spring配置,先用redis-cli在应用所在机器上手动执行一次PING命令。
本地能PING通,说明Redis进程和端口没问题,再检查Spring Boot配置里的host、port、password、database是否和redis-cli执行时使用的一致。本地也连不上,就要看Redis进程是否存活、端口监听是否正常、有没有防火墙或者云安全组拦着。很多内网环境只放行了6379,但集群或哨兵用的26379、7001这些端口没有放行,最后排查出一堆网络层面的问题。
如果是启动后一小时才开始报警,多半是连接池或者服务端连接数达到上限了。Lettuce单连接模型在超高并发下也会遇到连接耗尽,检查Redis的maxclients和服务端连接数,同时看应用日志里有没有RedisConnectionFailureException。
6.2 配置前缀错误导致什么都没生效
前面反复强调Spring Boot 2.x用spring.redis.,Spring Boot 3.x用spring.data.redis.。有一个很容易误解的场景是:项目升到Spring Boot 3后,旧配置还留在yml里,启动时并没有报错,因为Spring Boot不再认识这个前缀,也不会强行校验未知配置项,结果RedisTemplate拿到的连接工厂完全没有使用你填的那台Redis,默认走127.0.0.1:6379。
遇到这种很诡异的故障,排查办法很简单:在启动日志里搜Redis相关AutoConfiguration的日志,或者直接打印RedisConnectionFactory连接的地址。如果你看到host还是默认值,第一反应就应该是配置前缀写错了。
6.3 哨兵切换后应用迟迟连不上新主节点
哨兵完成了自动切换,应用却仍然报错或者无法恢复,这种现象在早期Redis客户端里比较常见。因为客户端缓存了旧master的连接信息,哨兵切换后客户端要继续保持旧连接直到连接被断开。
Spring Data Redis默认使用的Lettuce在新版本中已经支持自动刷新集群和哨兵拓扑,但部分Spring Boot版本需要显式开启。如果是哨兵模式,重点检查连接工厂用的是不是LettuceConnectionFactory,并且确认sentinel配置里的master名称和哨兵配置文件里的master名称完全一致。大小写不一致、多了个空格,都会导致客户端反复从哨兵拿不到正确的master地址。
实在不行,就把Java配置里的RedisSentinelConfiguration拉出来,在测试代码里主动调用getMaster方法看看返回的节点地址是否符合预期。这能快速区分是哨兵配置问题还是客户端引用问题。
6.4 集群模式下的MOVED和CROSSSLOT错误
集群模式下如果配置只写了一个节点,客户端发送key请求时,如果那个key的槽位不在该节点,会收到MOVED重定向。Spring Data Redis内置了底层重定向处理,只要max-redirects配置合理就能自动跟随。但假如你在一个操作里同时访问了好几个key,而这些key没有落在同一个哈希槽,就会返回CROSSSLOT错误。
解决办法不是去关掉集群,也不是提升max-redirects。而是要从业务上调整key的设计,使用哈希标签把需要绑定操作的key固定到同一个槽位。Redis的哈希标签规则很简单:key里有花括号时,key的实际哈希只针对花括号内的字符串,比如cache:{order}:1001和cache:{order}:1002的哈希槽只由order决定。
6.5 数据乱码和反序列化异常
系统运行正常,但Redis Desktop Manager里全是类似\xAC\xED\x00\x05t的乱码,这是默认JDK序列化器在作怪。此时的value虽然也能正常读写,但运维人员排查缓存时非常痛苦,而且JDK序列化对象还要考虑类版本兼容问题,稍微改动类结构就可能反序列化失败。
还有一种情况是使用了GenericJackson2JsonRedisSerializer之后,自己用String类型写入了缓存,再用RedisTemplate读取,结果类型转换报错。这通常是因为value序列化器不统一。最简单的做法是:纯字符串的缓存用StringRedisTemplate,对象缓存用自定义的RedisTemplate,两套模板各自命名清楚,避免混用。
6.6 连接池耗尽和超时参数调优速查
| 现象 | 可能原因 | 处理思路 |
|---|---|---|
| Connection timeout | Redis单机过载或网络拥塞 | 调大timeout,检查Redis slowlog |
| Redis connection pool exhausted | max-active过小或连接泄漏 | 检查业务是否正确关闭连接,调大max-active |
| NOAUTH Authentication required | password未填写或写错 | 和redis.conf中requirepass对比 |
| MOVED重定向 | 集群节点路由信息过期 | 开启Lettuce集群拓扑刷新,或适当调大max-redirects |
| CROSSSLOT操作受限 | 集群下多key操作跨槽 | 用哈希标签聚合key槽位 |
| 哨兵切换后重连慢 | 客户端拓扑刷新关闭或配置错误 | 打开刷新机制,检查sentinel master名称 |
连接池参数最好不要拍脑袋定。先压测得到TPS和平均耗时,然后计算每分钟新增连接数,再看空闲连接被回收的速率,据此调整max-active和max-idle。我见过一个项目把max-active设成512,结果高并发下一堆线程挤在连接池等待,Redis本身却非常空闲。连接池的合理值一般和业务线程池大小、IO耗时强相关,不是越大越好。
如果你现在还在调试阶段,我个人的建议是先跑通单机模式,把序列化、连接池、模板注入这些基础都验证一遍,再逐步升级到主从、哨兵、集群。每一步切换都单独做一次验证,别一次性把所有节点都上线。尤其是集群和哨兵模式,部署脚本越稳重,后续故障越少。
最后分享一个小技巧,我在项目里习惯给不同的Redis连接工厂起明确的bean名称,比如redisTemplateSingle、redisTemplateSentinel、redisTemplateCluster。这样在代码里看到哪个模板报错,就能直接对应到配置,不用再花时间推测当前代码使用的是哪套连接。处理Redis这种中间件,最大的成本永远不是部署,而是出了问题以后还要靠猜来定位原因。