☰
Spring Boot配置Redis四种模式:单机、主从、哨兵、集群全解析
2026/10/17 14:43:43 网站建设 项目流程

做后端开发这几年,遇到过不少同事因为Redis配置问题被线上环境折腾得焦头烂额。最常见的画面就是:本地单机Redis一遍过,一到测试环境接哨兵或者集群,应用要么启动报连接失败,要么运行一段时间突然断线重连,日志刷得人心烦。很多人以为Spring Boot里配置Redis就是填上host、port、password完事,实际上Redis的四种运行模式——单机、主从、哨兵、集群——背后对应了完全不同的连接机制和拓扑感知方式,配置文件稍微写错一个层级,应用和数据层就是两个世界。

这篇文章我把这四种模式在Spring Boot框架下的配置方式从头到尾梳理一遍,包括每种模式的核心原理、完整的配置案例、以及我在真实项目里踩过的坑。不管你现在用的是Spring Boot 2.x还是3.x,只要还在用Redis,这篇文章都值得你花十分钟看完。我会把每个配置项背后的“为什么”也讲清楚,而不是直接丢给你一段配置让你抄。

1. 为什么同一个Redis,四种配置写法天差地别

1.1 先理解连接工厂这个总开关

Spring Boot连接Redis,真正干活的组件叫RedisConnectionFactory。Spring Data Redis里所有操作,无论是RedisTemplate还是StringRedisTemplate,底层都绕不过这个工厂。我们配置Redis的各种模式,本质上是告诉Spring Boot:“你应该用哪种拓扑结构去创建连接工厂”。

单机模式对应的是最简单的standalone连接,直接一个host一个port,连上就完事。哨兵模式需要用RedisSentinelConfiguration,它会从哨兵节点获取当前master地址,并且在主节点故障切换后自动感知新master。集群模式则对应RedisClusterConfiguration,客户端需要知道所有节点地址,并且具备槽位路由和重定向处理能力。

主从复制在Spring Boot里比较特殊,因为Spring Data Redis原生并没有为“读写分离”提供一站式的自动配置。它不像哨兵有spring.data.redis.sentinel.*,也不像集群有spring.data.redis.cluster.*,主从完全需要你自己手工组装连接工厂。这个点我后面专门展开讲,很多人就是卡在这里。

1.2 四种模式,本质上是四种拓扑

先不说配置代码,我们把四种模式在拓扑层面搞清楚。

单机模式就是一台Redis实例,所有数据都在这一台上。写入、读取、高可用、扩展性,全都没有退路,实例挂了服务就断了。

主从复制模式是一主多从,主节点负责写,从节点负责读。数据通过异步复制同步到从节点。它解决了读压力的分担,但master挂了之后谁来顶上?如果没人管,写服务就彻底不可用。所以主从只能算“读写分离方案”,不算完整的高可用方案。

哨兵模式在主从基础上加了哨兵进程。哨兵通过心跳监控主节点状态,主节点挂了之后,哨兵会在从节点中选举一个提升为新的master,并通知客户端新地址。这套机制让Redis真正具备了自动故障转移能力。

集群模式是另一个维度,它通过哈希槽把数据分片到多个主节点上,每个主节点又可以挂从节点。集群同时解决了数据容量问题、写扩展问题、以及一定程度的高可用问题。但相对应的,客户端复杂度、运维复杂度都上了一个台阶。

提示:单机、主从、哨兵本质上都是“全量数据副本”模式,集群才是真正的数据分片模式。选型时先想清楚你到底是容量不够、读压力大、还是要高可用,这就决定了你要配哪种。

2. 依赖与基础配置:先把自己的地基打好

2.1 依赖引入和版本选型

无论用哪种模式,第一步都是引入spring-boot-starter-data-redis:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

Spring Boot 2.x和3.x在这个starter内部默认集成的客户端都是Lettuce。Lettuce基于Netty,线程安全,支持同步、异步、响应式三种编程模型。如果你所在的团队以前习惯用Jedis,也能替换:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>

但我的建议是,新项目直接用默认的Lettuce就好。Jedis在哨兵模式下的某些旧版本确实有成熟的主从感知逻辑,但Lettuce到2.x、3.x版本之后,拓扑刷新能力已经跟上了,没必要额外引入一套客户端增加变量。

版本兼容上要特别注意一个点:Spring Boot 3.x基于Jakarta EE,并且强制要求Java 17以上。如果你还在用Java 8,老老实实用Spring Boot 2.7.x。Redis服务器版本方面,Redis 5.0以上基本都能无缝支持,Spring Data Redis 3.x对Redis 6.x、7.x的支持都没问题。

2.2 序列化这一关,多数人第一次栽跟头

配置Redis最容易被忽略但影响最大的环节其实是序列化。Spring Data Redis默认使用JdkSerializationRedisSerializer,它会把你存进去的对象序列化成二进制,看起来像乱码,而且可读性极差。更坑的是,如果对象类定义的序列化ID变了,反序列化直接失败。

建议在项目里显式声明一个RedisTemplate Bean,把key和hashKey用StringRedisSerializer,value和hashValue用GenericJackson2JsonRedisSerializer:

@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer keySerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); return template; } }

提示:如果你的Redis要跟其他系统共享数据,比如同一个Key由Java端写入、Python端读取,建议配置ObjectMapper时显式指定类型信息,并约定好日期格式。跨语言场景下,GenericJackson2JsonRedisSerializer的默认行为有时会带上@class字段,这既是便利也是隐患,反序列化会因此存在安全风险,需要确认你的数据源可信。

2.3 连接池与Lettuce的暗坑

Lettuce默认情况下所有线程共享同一个连接,这在低并发场景下没有问题,但高并发时可能会出现竞争和排队。Spring Boot 3.x中,只要你在classpath里加入commons-pool2依赖,Lettuce连接池才会被自动创建:

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>

不引入这个依赖,配置里的spring.data.redis.lettuce.pool.*会被直接忽略,应用默默退化成无池模式。这个坑隐蔽得很,不少团队加了池配置,但因为没有commons-pool2依赖,池根本没生效,直到高并发压测才发现。

2.4 Spring Boot 2.x和3.x的配置差异

Spring Boot 2.4版本之前,Redis的配置前缀是spring.redis.*。从2.4开始,Spring Boot把所有中间件的配置统一收敛到spring.data.*命名空间下,Redis对应的前缀变成了spring.data.redis.*。

我在网上看到不少教程还在用旧的spring.redis.host,在Spring Boot 3.x里这种配置是完全不生效的,启动时不会有任何报错,但应用会去连localhost:6379,然后莫名其妙连不上。如果你在维护老项目,也留意一下这个问题。

3. 单机模式:最简单,却经常被忽略细节

3.1 单机配置的完整过程

单机模式是最基础的模式,配置文件长这个样子:

spring: data: redis: host: 192.168.1.10 port: 6379 password: yourpassword database: 0 timeout: 3s connect-timeout: 2s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 300ms

这里字段不多,但每个都值得解释一下。

timeout是命令执行的超时时间,不是建立连接的超时时间。你发一个GET命令,如果Redis在3秒内没响应,客户端抛超时异常。connect-timeout是TCP建连超时,两者要分开配。

max-active是连接池最大连接数,默认值是8。如果你的应用有多个业务线并发访问Redis,8个可能不够。但也不是越大越好,每个连接都有开销,压测时从8开始逐步往上调,观测线程阻塞情况和RT变化。

max-wait是连接池获取连接的等待时间。如果连接池被占满且等待时间超过这个值,会直接抛JedisException或Lettuce的异常。这个值建议不要配成-1(无限等待),否则某个Redis故障时,所有请求线程会全部卡死等待连接,直接把应用拖垮。

3.2 为什么单机模式也要设置超时和连接池

有些开发觉得单机模式反正直连一台机器,没必要配那么多参数。我有一次排查线上接口变慢,发现就是Redis连接池默认参数导致的:某个业务线程持有连接后因为一条慢查询命令卡了很久,其他线程在连接池排队,最后接口大面积超时。连接池参数不是防Redis宕机,而是防客户端资源耗尽。好好配池,就是在保护你的应用线程池。

3.3 单机模式适合什么场景

单机模式适合开发环境、测试环境、以及数据量小、并发不高的生产场景。典型例子是:某个内部管理系统缓存一些字典数据,总量不超过几百MB,QPS不到几百。这时候用单机完全没问题,不需要为了“高大上”硬上集群。越简单的架构越稳定,出了问题也越好排查。

但单机模式在生产环境有个非常现实的问题:没有冗余。机器断电、进程OOM、网络分区,任何一个故障都会让你的缓存风暴直接打到DB上。所以生产环境但凡核心链路依赖Redis,我建议最少也得上哨兵。

4. 主从复制模式:读多写少的标配

4.1 主从复制的原理要点

主从模式下,主节点处理写命令,把写命令记录到replication backlog缓冲区,从节点异步拉取这些命令并执行,从而保持数据一致。这套机制有两点要特别留意:第一,复制是异步的,主节点写完数据后不会等待从节点确认。第二,断线重连后会进行部分同步或者全量同步,取决于offset是否还在backlog缓冲区范围内。

在Spring Boot应用层面,主从模式并没有专门的自动配置支持。这是很多人找遍源码都能看到哨兵配置类、集群配置类,却找不到主从配置类的原因。遇到这种情况,你需要自己来组装。

4.2 Spring Boot里怎么配置主从

我用过的方式有两种,各有优缺点。

第一种,最简单粗暴:定义两个连接工厂、两个RedisTemplate。一个连master,一个连slave。写操作走masterTemplate,读操作走slaveTemplate。Service层根据方法职责显式选择用哪个模板。

@Configuration public class MasterSlaveConfig { @Bean(name = "masterRedisTemplate") public RedisTemplate<String, Object> masterRedisTemplate() { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(masterConnectionFactory()); // 序列化配置省略 return template; } @Bean(name = "slaveRedisTemplate") public RedisTemplate<String, Object> slaveRedisTemplate() { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(slaveConnectionFactory()); // 序列化配置省略 return template; } @Bean public RedisConnectionFactory masterConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName("192.168.1.10"); config.setPort(6379); return new LettuceConnectionFactory(config); } @Bean public RedisConnectionFactory slaveConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName("192.168.1.11"); config.setPort(6379); return new LettuceConnectionFactory(config); } }

这种方式的优点是直观、好控制,缺点是应用代码里要区分读模板和写模板,业务代码里一个不小心就用错了。而且如果从节点故障,读链路直接挂。

第二种,用Redisson。Redisson原生支持主从模式配置,而且自动做读写分离:

@Configuration public class RedissonConfig { @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useMasterSlaveServers() .setMasterAddress("redis://192.168.1.10:6379") .addSlaveAddress("redis://192.168.1.11:6379") .setPassword("yourpassword") .setReadMode(ReadMode.SLAVE) .setMasterConnectionMinimumIdleSize(5) .setSlaveConnectionMinimumIdleSize(5) .setTimeout(3000); return Redisson.create(config); } }

Redisson的ReadMode.SLAVE会在从节点执行读操作,只有在没有从节点可用时才自动切换到master。这个方案的好处是自动读写分离,不用业务代码里区分模板。坏处是引入了一个重量级框架,而且Redisson的分布式锁、分布式对象等特性如果没用上,属于纯纯的性能浪费。

4.3 读写分离的两个实现思路

如果团队想在不引入Redisson的情况下做到自动化读写分离,可以用Spring AOP。核心思路是:定义一个自定义注解比如@ReadOnly,然后写一个切面,在方法执行前根据注解动态切换当前线程绑定的RedisTemplate。这个方案实现成本不低,而且对Lettuce连接池的资源复用要求较高,建议团队规模到了一定量级、有专职中间件维护人员再上。

还有一个务实路线:如果你的业务对数据一致性容忍度很高,比如读多写少的商品详情页缓存,其实不必在主从架构上费劲,直接用哨兵模式让客户端始终连master读写,把从节点纯粹当作备份节点和故障切换的备胎。这样应用配置最简单,也规避了复制延迟的不一致问题。但这样做主节点的读压力依然很大,属于“用冗余换高可用”的思路。

我在实际项目中最终采用的是双模板方案,简单可控。每次写操作后,如果有强一致需求,通过一个短暂延迟去读master。读多写少的场景一般都能接受毫秒级延迟,处理起来心里有数。

5. 哨兵模式:用三个进程换来真正的HA

5.1 哨兵到底在干什么

哨兵模式解决的是主从模式下“主节点挂了怎么办”的问题。正常情况下,哨兵进程监控所有Redis节点,当某个主节点超过down-after-milliseconds阈值没有响应心跳时,哨兵会先判断这个节点为“主观下线”。当多个哨兵都认为这个主节点下线时,就达成“客观下线”,此时开始故障转移流程。

故障转移的核心动作是从从节点里选出一个最合适的,执行slaveof no one把它提升为新master,然后让其他从节点重新指向新master。整个过程由哨兵集群协商完成,不需要人工干预。

5.2 Spring Boot的哨兵配置

Spring Boot针对哨兵模式提供了开箱即用的自动配置,你只需要告诉它哨兵节点在哪、master叫什么名字:

spring: data: redis: sentinel: master: mymaster nodes: - 192.168.1.10:26379 - 192.168.1.11:26379 - 192.168.1.12:26379 password: yourpassword timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 sentinel: # Spring Data Redis 3.x 支持的哨兵用户名/密码,如果你的哨兵也开启了认证 username: password:

注意几个关键点。第一,一旦配置了sentinel.master和sentinel.nodes,就不要在配置里混写host和port,否则连接工厂会优先按单机模式解析,导致应用连接的就是你填的那个普通Redis实例,而绕过了哨兵的master发现流程。第二,spring.data.redis.password在实际使用时,通常指Redis实例的密码;如果你的环境为哨兵单独设置了认证,还需要在sentinel.password里单独配置。第三,Lettuce默认在连接哨兵时会通过SENTINEL get-master-addr-by-name命令动态获取当前master地址,所以即使故障转移把master切走了,客户端也能自动感知到新地址,并且建立新连接池。

5.3 故障转移期间的连接池陷阱

哨兵模式的故障转移不是瞬间完成的。从master宕机到哨兵完成选举、再通知客户端,这个时间窗口通常有几秒到十几秒。在这个窗口内,应用确实会看到连接异常。

但这里有个容易被忽略的坑:Lettuce的拓扑刷新频率。如果你用的是Spring Boot 2.x + Lettuce 6.x,拓扑刷新默认是关闭或低频的。当哨兵切换master之后,Lettuce虽然从哨兵节点能感知到新地址,但已建立的旧连接可能还残留在连接池里。我在某个项目里就遇到过这种情况:故障转移完成后,应用过了一分多钟才恢复,而哨兵那边早就完成切换了。

解决思路有两个。第一个是正确配置Lettuce的拓扑刷新:

spring: data: redis: lettuce: cluster: refresh: adaptive: true period: 5s

不过需要留意,这个配置主要面向集群模式,哨兵模式下的拓扑刷新在Spring Data Redis里还依赖LettuceConnectionFactory编程式设置。

第二个思路更实用:在应用层做兜底,比如给Redis操作加上简单的重试机制。当抛出连接异常时,重新从连接池获取连接,往往就能拿到新master地址。

5.4 哨兵模式的合适场景

如果你的Redis数据量还在单机可承受范围内,但服务需要7x24小时可用,哨兵模式是最成熟、最稳妥的选择。很多中小型公司的核心缓存、会话数据、热点数据,都是“若干实例+三哨兵”的配置。它足够稳定,运维复杂度也可控。

6. 集群模式:数据分片才是大规模问题的解药

6.1 Redis Cluster的核心机制

Redis Cluster通过哈希槽实现数据分片,整个集群共16384个槽位。每个key计算CRC16哈希后对16384取模,决定它落在哪个槽,每个主节点负责一部分槽位。客户端拿到key后,需要根据槽位路由到正确的节点执行命令。

当集群槽位发生变化,比如主节点迁移、扩缩容、主从切换,客户端会收到MOVED错误或ASK错误。MOVED意味着槽位已经永久迁移到另一节点,客户端需要更新本地路由缓存;ASK意味着槽位正在迁移过程中,客户端需要临时转发请求。

6.2 Spring Boot的集群配置

Spring Boot对集群模式的配置同样有专门支持:

spring: data: redis: cluster: nodes: - 192.168.1.10:7001 - 192.168.1.11:7002 - 192.168.1.12:7003 max-redirects: 3 password: yourpassword timeout: 3s connect-timeout: 2s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2

集群模式下nodes只需要配置一部分节点即可,Lettuce启动时会通过CLUSTER SLOTS命令拉取完整的槽位分布和节点列表。所以即使你只写了三个节点地址,只要集群有其他节点,它也能自动发现。

max-redirects控制的是当命令因为槽位变化被重定向时,客户端最多跟随重定向的次数。默认值是5,一般3到5都合理。如果实际重定向次数超过这个值,说明集群拓扑很不稳定,通常正在扩缩容或网络分区。

6.3 集群模式下必须处理的三个问题

第一,多key操作限制。Redis Cluster里,MGET、MSET、DEL这类涉及多个key的命令,要求所有key必须落在同一个槽位。如果业务确实需要跨key操作,可以通过hash tag来实现:key中写入花括号,Redis只会对花括号内的部分做哈希,比如{user:123}:profile和{user:123}:orders会落在同一槽位。但注意,hash tag会导致数据分布倾斜,滥用会让某些节点的数据量明显大于其他节点。

第二,数据库限制。集群模式下只能使用db0。如果你在单机时代习惯了把不同业务放进Redis的不同database,比如缓存用db0、session用db1,那切到集群后会直接收到ERR SELECT is not allowed in cluster mode错误。解决方案是使用不同的key前缀区分业务,或者拆分成多个独立集群。

第三,事务和Lua脚本的约束。集群模式下,事务中的所有key必须落到同一槽位,Lua脚本也一样。对于需要原子性操作的业务,在设计key时就要保证相关的key能通过hash tag落到一起。否则你会发现,单机模式下调试得好好的分布式锁代码,一上集群就偶尔报CROSSSLOT错误。

6.4 集群模式里的分布式锁注意点

这一点值得单独说。集群模式下实现分布式锁,主流的Redisson分布式锁方案底层涉及Lua脚本,它内部会把锁key和看门狗续期key通过hash tag固定到同一槽位,所以常规使用没问题。但如果你自己造的轮子是基于SET key value NX EX加锁,然后解锁时用Lua脚本校验value再删除,一定记得把锁key本身设计好。涉及多个key的Lua脚本,在集群中必须保证所有key在同一槽位,否则会报跨槽错误。

我在某支付类项目里就踩过这个坑:团队从哨兵迁移到集群后,分布式锁在高峰期偶尔失效,排查发现是自定义锁脚本里同时操作了锁key和一个计数器key,两个key被哈希到了不同槽位。后来把计数器key改名成带相同hash tag的形式才解决。

7. 四种模式怎么选:一张对比表格讲透

7.1 关键维度的横向对比

为了让你更直观地判断,我把四种模式的核心维度整理成了一张对比表格:

对比维度单机模式主从复制哨兵模式集群模式
数据分片无无无16384个哈希槽
数据容量单机内存上限单机内存上限单机内存上限水平扩展,容量随节点增加
写扩展能力不可扩展不可扩展,master单点写不可扩展,master单点写多master分片写
读扩展能力不可扩展可增加从节点可增加从节点每个分片可增加从节点
高可用无无自动故障转移哨兵自动故障转移主节点故障自动提升从节点
配置复杂度低中中高
Spring Boot原生支持完善不支持自动配置完善完善
典型使用场景开发测试、极小型应用读多写少、可接受主节点故障中小规模、核心链路高可用大规模数据、高并发写入

7.2 按业务场景选型的具体建议

给大家一个比较实用的选型过程,按照下面的问题顺序判断就好:

第一问:数据量会不会超过单机内存?如果明确会超过,直接考虑集群模式。不要为了省事硬扛单机,Cache爆内存导致OOM的线上事故我见过太多。

第二问:是否需要自动故障转移?如果不需要,例如只是内部工具、临时缓存,那单机模式足够,别给自己增加维护负担。

第三问:如果存在单点风险,读多写少且能接受主从切换空窗期?主从模式或者哨兵模式都可以考虑。重点看团队有没有能力维护哨兵的三个进程,如果运维能力有限,建议不引入额外组件,就用主从,并接受主节点故障后要手工切换的现实。

第四问:业务是否涉及复杂跨key操作?如果有较多的MGET、Lua脚本、事务需求,且团队对哈希槽机制理解不深,集群模式会大幅增加开发成本。这个时候宁可多做几个哨兵集群,按业务拆key,也不要把所有东西塞进一个大集群。

选型没有绝对答案,最怕的是为了“技术先进”而选集群,结果团队天天在跨slot错误里挣扎。我在项目里更倾向于“先够用,再演进”,单机支撑不了的直接上哨兵,哨兵支撑不了再平滑迁到集群。

8. 实战踩坑记录:我遇到过的几个经典问题

8.1 启动报错排查:RedisConnectionFailureException

这个异常是所有Redis问题里出现频率最高的。排查顺序一般是:

检查连接配置是否生效。特别是Spring Boot 3.x下如果还在写spring.redis.*,配置静默失效,应用会尝试连localhost。通过/actuator/env能看到有效配置,或者直接用--debug启动查看自动配置报告。

检查网络连通性。命令行先telnet ip port,确认TCP链路没问题。很多时候问题出在防火墙或安全组,尤其云环境里Redis端口只对特定IP开放。

检查密码。密码错误会直接抛ERR Client sent AUTH, but no password is set或者WRONGPASS invalid username-password之类异常。注意Redis 6.0开始支持ACL,如果账号没有正确授权某条命令的权限,也会在运行期报NOPERM。

还有一种容易被忽视的情况:应用部署在容器中,DNS解析到了旧IP。我在某公司排查一个持续半天的连不上问题,最后发现是K8s的Service对应的Endpoints里混了一个已下线的旧Pod IP。

8.2 运行期偶发超时:Lettuce共享连接带来的问题

之前提过,Lettuce默认所有线程共享同一条连接。如果你没配连接池,但业务并发很高,偶发的RedisCommandTimeoutException就会冒出来。因为某条慢命令阻塞了共享连接,其他命令只能排队等待。

排查时可在代码里临时给连接工厂设置shareNativeConnection(false),让每个线程在池中获取独立连接。如果不方便改配置,也可以直接压测验证。

8.3 缓存穿透与缓存雪崩的连带表现

Redis模式切换后,很多人会发现缓存命中率下降、DB压力异常增大。这往往不是Redis配置问题,而是应用层没有处理好哪些key承载热点、哪些key允许为null。换个模式并不会自动解决缓存穿透,核心还是在查询DB后把null缓存起来,给过期时间加随机因子防止雪崩。

8.4 连接池参数调整后的重启时机

连接池参数不是改完配置立刻生效的。很多连接池在运行时已经创建了若干空闲连接,调整后需要重启应用才能完全重建连接池。我在线上遇到过调整max-idle后,旧空闲连接占着不释放的情况,配置看起来生效了,实际上客户端还持有旧连接句柄。如果你在排查连接数异常,可以先重启验证。

8.5 大Key和热Key问题

这个问题跟模式本身无关,但会在切换模式后暴露得更明显。比如单机模式下一个大Key几百MB,复制到集群时会严重拖慢全量同步和槽位迁移。热Key则在集群模式下可能导致某个分片节点CPU跑满,其他节点闲置。

常规做法是拆分大Key,比如把一个大Hash按业务维度拆成多个小Hash;热Key可以加本地缓存或做多副本读。这些都是老生常谈,但确实常遇到。

8.6 Spring Cache注解在集群模式下的特殊行为

如果你在Spring Boot里用了@Cacheable注解,默认的key生成规则是SimpleKeyGenerator,最终生成的key格式类似cacheName::key。在集群模式下,这个key的槽位由整个字符串决定,本身没问题。但如果你自定义了key生成器,生成结果包含形如{xxx}的内容,注意这会被Redis Cluster当作hash tag处理。一旦两个缓存key的花括号部分恰好一样,它们会被强制路由到同一节点,造成数据倾斜。

另外Spring Cache默认使用JVM本地锁来防止缓存击穿,在多实例部署时会有局限。集群模式下我建议自己实现缓存层,控制力更强。

我个人在实际操作中的体会是:Redis配置这件事,很多问题都是拓扑理解不到位引发的。你把连接工厂当成一个“翻译官”,它把你的配置翻译成客户端能理解的连接方式,翻译错了,其他全是连锁反应。所以遇到连接异常,先不要急着改代码,先回到配置层级,确认你的Redis当前到底跑的哪种模式,你的配置文件翻译对了没有。

最后再分享一个实用技巧:不管用哪种模式,项目里一定要把RedisClientName和连接指标暴露到监控系统。Lettuce自带基于Micrometer的指标支持,能看到连接数、空闲数、命令耗时等关键指标。Redis故障不可怕,可怕的是你连它什么时候开始抖动都不知道。等线上先炸了再排查,那才是真的被动。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询