☰
Spring Boot四种Redis模式:配置、选型与避坑指南
2026/10/14 7:28:56 网站建设 项目流程

1. 为什么同一个Spring Boot项目要切换四种Redis模式

前阵子有个朋友问我,说他在本地起了一个Redis,Spring Boot项目里配了spring.redis.host和spring.redis.port,结果程序一启动就报连接失败。我问他Redis是不是在虚拟机里、端口有没有被占用,他说这些都查过了。后来我让他看一眼项目里是不是引入了某个集群依赖,果然,他之前照着某篇文章配过spring.redis.cluster.nodes,虽然注释掉了,但配置文件里还留着个空格——Spring Boot一读到这个属性,就觉得你要用集群模式,根本不会走单机连接逻辑。

这就是Redis四种模式在Spring Boot框架下配置时最容易踩的坑:你以为你配的是单机,框架却以为你要连集群。所以这篇文章我把四种模式的配置完整梳理了一遍,包括每种模式在application.yml里怎么写、底层连接工厂如何创建、常见的启动失败和运行时异常怎么排查。适合刚接触Redis的Spring Boot初学者,也适合已经用上单机模式、准备往主从、哨兵或集群方向升级的团队。

先统一说背景:下面所有配置都以Spring Boot 2.7和Spring Boot 3.2为例,Redis服务端版本是7.0。如果是更老的项目,比如Spring Boot 1.5,配置方式差异比较大,建议先升级依赖再参考本文。

Redis的四种模式,本质上不是四种数据类型,而是四种部署架构:

  • 单机模式:一台Redis服务器,最简单,适合开发环境和并发量不高的业务。
  • 主从复制模式:一台主库负责写,多台从库负责读,数据实时同步到从库。
  • 哨兵模式:在主从复制基础上加一层监控,主库挂了能自动把某个从库提升为新主库。
  • 集群模式:多台Redis节点组成一个整体,数据按槽位分片存储,每个节点还带从节点保证高可用。

每种模式在Spring Boot里的配置差异很大,不过核心入口都是RedisConnectionFactory。只要把连接工厂搞清楚,换模式其实就是换一套配置的事。

1.1 从最容易出问题的配置优先级说起

Spring Boot 2.0之后,Redis自动配置类在处理连接工厂时,会按照一个固定顺序判断当前应该创建哪种模式。这也是我那位朋友项目出问题的根因。

优先级大致是这样:

  1. 如果配置了spring.redis.cluster.nodes,优先创建集群连接工厂。
  2. 否则,如果配置了spring.redis.sentinel.master和spring.redis.sentinel.nodes,创建哨兵连接工厂。
  3. 否则,走单机模式,读取spring.redis.host和spring.redis.port。

所以,哪怕你把单机配置写得好好的,只要配置文件里残留了任何集群相关属性,框架就会鬼使神差地连不上。我在实际项目里还见过有人把集群节点地址写进了哨兵配置里,结果客户端一直尝试连接哨兵端口,报错信息却是Connection refused,让人一头雾水。

排查这类问题,我一般先看生效的配置是什么:

# 启动时打印Redis配置 java -jar your-app.jar --debug

然后去日志里找RedisProperties相关的输出,看cluster、sentinel字段是不是有值。如果看到clusterNodes=[]但sentinel有值,说明配置被意外读取了。

1.2 选型的核心判断依据

四种模式不是越复杂越好。我给一个非常朴素但管用的判断方法:

  • 只有一台Redis,不需要考虑挂掉的情况,选单机。
  • 读请求远高于写请求,写库压力不大,但读库撑不住,选主从复制。
  • 需要在主库故障时自动切换,让应用不感知Redis节点变化,选哨兵。
  • 数据量已经超过单机内存容量,或者未来明确会超过,选集群。

最容易误判的是数据和读写压力之间的关系。我见过不少团队数据量不到2GB、QPS也就几千,却急匆匆上了集群,结果运维成本和客户端连接问题一大堆,性能反而还不如单机跑得快。Redis集群模式里,一次mget可能被拆分到多个节点,跨槽位的操作还要额外开销,不是免费的。

下表是我自己整理的一个选型速查表:

模式部署节点数自动故障转移数据分片客户端连接复杂度推荐场景
单机1不支持不支持最低开发测试、低并发、可容忍宕机
主从复制至少2不支持不支持偏低读多写少,备份诉求强
哨兵至少3支持不支持中等生产环境单机容量够用但需高可用
集群至少6支持支持较高数据量超过单机内存或高并发写入

现在回看开头的那个问题,他其实只需要单机模式,却因为残留配置走了别的分支。所以后面每一节,我都会放一份完整的、可复制的YAML配置,并标注哪些字段是可选的、哪些是必须的,防止配置文件里出现“幽灵配置”。


2. 动手前先打好的地基:连接池选择与Spring Boot版本差异

先别急着写YAML。Redis在Spring Boot里有两种客户端实现,一个是Lettuce,一个是Jedis,它们在连接池、集群拓扑感知上差别非常大。Spring Boot 2.0之后的默认客户端是Lettuce,但依然保留了Jedis的自动配置支持。

2.1 Spring Boot版本决定配置前缀

这是最基础却最容易被忽视的点。Spring Boot 2.x的Redis配置前缀是spring.redis,从Spring Boot 3.0开始,配置前缀统一改成了spring.data.redis。如果你拿3.x的配置去跑2.x项目,或者反过来,启动时会发现配置完全不生效,但又不报明显错误,因为属性名对不上时Spring Boot只会在debug日志里打一条提示。

我通常维护两个基线版本:

  • Spring Boot 2.7:用spring.redis.*
  • Spring Boot 3.x:用spring.data.redis.*

下面所有举例我统一使用Spring Boot 3.x的写法。如果你还在用2.x,把前缀前加上data去掉即可。为了省事,有些项目会在application.yml里同时保留两份,但我不建议,因为Spring Boot 3.x已经不再绑定spring.redis开头的配置,留在那里只会误导人。

2.2 Lettuce与Jedis的关键差异

维度LettuceJedis
底层模型Netty异步,单连接可多线程复用BIO,每个线程一个连接
默认连接池不强制,但推荐配commons-pool2必须配连接池
集群拓扑刷新支持自动刷新,可配置触发条件需要手动重建连接
哨兵故障切换感知可自动重新获取master地址旧版本的Jedis需要手动刷新

Lettuce的核心优势是连接复用,一个物理连接可以被多个线程并发使用,在高并发场景下内存占用比Jedis低。但有个隐藏问题:因为连接是共享的,如果你在代码里对同一个连接执行阻塞性操作,比如大数据量的scan或者慢查询,可能影响其他线程。

Jedis就简单粗暴一些,每次从连接池拿连接,用完归还。它的行为更符合直觉,但在连接池耗尽时容易出现线程阻塞。

Spring Boot的默认选择是Lettuce,除非有明确原因,我不建议为了“熟悉”而换回Jedis。不过后面讲集群模式时,我会专门说一个Jedis在集群环境下的优势,那是Lettuce的一个痛点。

2.3 引入依赖,别漏掉连接池

使用Lettuce时,很多人以为只要引入spring-boot-starter-data-redis就行,结果一开高并发就报RedisConnectionFailureException。原因是默认没引入连接池实现。

下面是完整的Maven依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Lettuce连接池,记得一定要加 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>

如果要用Jedis,先把默认的Lettuce排除掉,再加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跑通,不要一开始就切换客户端。因为网上大量教程和踩坑文章都是针对默认客户端的,你换掉它,遇到问题能参考的资料会少很多。


3. 单机模式配置:最容易被忽略的默认行为

单机模式是四种模式里配置量最小的,但偏偏越简单越容易出问题。我先给出一份实际项目里在用的配置,然后逐个字段解释为什么要这样写。

3.1 一份可以直接抄的单机YAML

spring: data: redis: host: 192.168.1.100 port: 6379 password: your-redis-pass database: 0 timeout: 3s lettuce: pool: enabled: true max-active: 16 max-idle: 8 min-idle: 0 max-wait: 3s shutdown-timeout: 200ms

看似平平无奇,但每一个字段都有人翻过车。database默认是0,如果你用的是云服务商的Redis,实例通常只开放db0,配置其他数据库号启动时不会报错,但存入的数据全部写到另一个逻辑库里,业务读取时又查不到。我自己排查过一个诡异问题,开发环境一切正常,测试环境数据就是不对,最后发现测试环境的配置里写了database: 2,而云Redis默认只有db0。

timeout指的是连接超时和读取超时,默认好像是60秒(不同版本不太一样),对于要求快速失败的业务来说太久了。我习惯配成3秒,让调用方尽早感知Redis异常,而不是卡住线程等一个注定失败的请求。

lettuce.pool这块要特别注意,Spring Boot 2.x后期版本和3.x都有一个enabled字段,不写的话,即使你配置了max-active,连接池也不一定启用。很多初学者的配置里只写了max-active: 8,结果压测时发现连接数飙升到几百,才发现没启用。

3.2 启动时检查是否真的连上了

配置完成后,我不建议直接跑业务逻辑,可以先用一个最简单的测试用例验证配置有效性:

@SpringBootTest class RedisConnectionCheckTest { @Autowired private StringRedisTemplate stringRedisTemplate; @Test void shouldConnectToRedis() { stringRedisTemplate.opsForValue().set("health:check", "ok"); String value = stringRedisTemplate.opsForValue().get("health:check"); System.out.println("read from redis: " + value); } }

如果启动时日志里出现Creating a shared RedisConnection,说明Lettuce连接工厂已经初始化成功。如果出现Unable to connect to Redis,先把防火墙、Redis配置文件里的bind和protected-mode检查一遍。

3.3 单机模式最隐蔽的坑:RedisTemplate序列化

这题看起来跟模式配置无关,但它会影响你在任何模式下使用Spring Boot的RedisTemplate。默认的RedisTemplate用的是JDK序列化,往Redis里写一个字符串,key会变成\xac\xed\x00\x05t\x00...这样的二进制字符。你在命令行里完全看不懂存了什么。

我建议使用连接工厂的时候,同时自定义一个统一的RedisTemplate,方便在所有模式里复用。下面这个配置类可以直接复制,里面用到了StringRedisSerializer和GenericJackson2JsonRedisSerializer:

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

这里不详细展开序列化的所有细节,但有一点提醒:GenericJackson2JsonRedisSerializer会在序列化结果里带上@class信息,反序列化时用它还原类型。如果你把Redis作为缓存中间件,允许其他地方直接修改数据,可能会遇到类型转换异常。这时候可以考虑换成Jackson2JsonRedisSerializer,并用一个公共DTO去读。


4. 主从复制模式配置:Spring Boot原生并不替你做读写分离

主从复制模式下,你会有至少两台Redis实例,一台主库(master),一台或多台从库(replica)。主库负责写,从库负责读,数据通过复制协议实时同步。Spring Boot本身没有任何自动路由逻辑,它只是帮你创建一个连接到“某个Redis地址”的连接工厂,不会因为你写了两个地址就自动区分读写。

4.1 先搭好Redis主从结构

在Spring Boot配置之前,先确保Redis服务端已经正确组好主从关系。测试环境可以用最简单的方式:

主库节点,修改redis.conf:

bind 0.0.0.0 port 6379 requirepass master-password

从库节点,修改replica.conf:

bind 0.0.0.0 port 6380 replicaof 192.168.1.100 6379 masterauth master-password

注意masterauth必须配置。如果主库开启了密码,从库同步数据时需要用这个密码向主库认证。这一步漏了,从库日志会不断报MASTER aborted replication with error: NOAUTH Authentication required,而且Spring Boot应用是正常启动的,因为应用连接的是从库端口,从库本身不需要密码。

验证主从是否正常,在主库执行:

redis-cli -p 6379 -a master-password info replication

输出里的connected_slaves:1表示从库连上了,master_repl_offset和从库的偏移保持一致说明数据在正常同步。

4.2 Spring Boot配置一对读写连接工厂

主从模式下,我会写两个LettuceConnectionFactory,一个指向主库,一个指向从库。这里放一个核心配置示例,基于Spring Boot 3.2:

@Configuration public class RedisReadWriteConfiguration { @Bean @Primary public LettuceConnectionFactory writeConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName("192.168.1.100"); config.setPort(6379); config.setPassword("master-password"); return new LettuceConnectionFactory(config); } @Bean public LettuceConnectionFactory readConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName("192.168.1.101"); config.setPort(6380); config.setPassword("replica-password"); return new LettuceConnectionFactory(config); } @Bean @Primary public RedisTemplate<String, Object> writeRedisTemplate( @Qualifier("writeConnectionFactory") RedisConnectionFactory factory) { return buildRedisTemplate(factory); } @Bean public RedisTemplate<String, Object> readRedisTemplate( @Qualifier("readConnectionFactory") RedisConnectionFactory factory) { return buildRedisTemplate(factory); } private RedisTemplate<String, Object> buildRedisTemplate( RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 序列化配置同上一节 return template; } }

注意两个点:主库工厂上加了@Primary,这样其他模块注入的RedisTemplate默认走主库写入;从库工厂单独命名,业务代码里需要读多写少时,显式注入readRedisTemplate。

@Service public class OrderService { private final RedisTemplate<String, Object> writeTemplate; private final RedisTemplate<String, Object> readTemplate; public OrderService( @Qualifier("writeRedisTemplate") RedisTemplate<String, Object> writeTemplate, @Qualifier("readRedisTemplate") RedisTemplate<String, Object> readTemplate) { this.writeTemplate = writeTemplate; this.readTemplate = readTemplate; } public void saveOrderCache(Order order) { writeTemplate.opsForValue().set("order:" + order.getId(), order); } public Object getOrderCache(String orderId) { return readTemplate.opsForValue().get("order:" + orderId); } }

这种写法比网上常见的“只配一个从库地址,把主库地址写死在代码里”要干净得多,后续如果从库扩容,只需加一个Bean,不需要改业务代码。

4.3 主从复制模式必须面对的延迟和故障问题

主从复制是异步复制。主库接写入后立刻返回成功,但数据同步到从库需要时间。这会导致刚写完主库、立刻读从库时,读不到数据。对于订单号这类能容忍最终一致性的场景没问题,但如果业务要求“写后必读一致”,就要在读操作前强制走主库,或者在业务上做短暂缓存穿透。

另一个大问题是,主从复制本身不提供故障转移。主库挂了,从库虽然有全部数据,但它不会自动变成主库,应用也不知道该连谁。这时候如果连接的是主库,整个写入链路就断了。真正要解决高可用,得靠哨兵模式。


5. 哨兵模式配置:客户端如何拿到变化的master地址

哨兵模式是在主从复制基础上增加了一层哨兵进程(Sentinel),它的核心职责是监控主库和从库的健康状态。当主库发生故障时,哨兵集群会选举一个新的主库,并通知客户端更新连接地址。

5.1 哨兵模式的典型部署结构

最少需要三个Sentinel节点,加上一个主库、一个从库,一共五个Redis进程。注意,部署Sentinel的机器不一定非要单独一台,可以和Redis节点混部,但生产环境最好独立部署。

Sentinel的配置文件可以很短:

port 26379 sentinel monitor mymaster 192.168.1.100 6379 2 sentinel auth-pass mymaster master-password sentinel down-after-milliseconds mymaster 5000

重点看sentinel monitor mymaster 192.168.1.100 6379 2:mymaster是主库逻辑名称,后面两个哨兵节点认为主库不可达时,才会触发故障转移。这个数字也叫“quorum”,配2意味着至少要两个哨兵达成一致。

5.2 Spring Boot连接哨兵的方式

在Spring Boot里配置哨兵模式,不是直接指定主库地址,而是把哨兵节点地址告诉客户端。客户端连接任意一个哨兵,询问“mymaster当前的主库是谁”,然后拿着真正的主库地址去建立连接。

spring: data: redis: password: master-password sentinel: master: mymaster nodes: - 192.168.1.11:26379 - 192.168.1.12:26379 - 192.168.1.13:26379 password: sentinel-password timeout: 3s lettuce: pool: enabled: true max-active: 16 max-idle: 8

配置文件里有两处密码,非常容易搞混:

  • spring.data.redis.password:Redis数据节点的密码。哨兵模式下,主库和从库可能都有requirepass,它是客户端向数据节点认证用的。
  • spring.data.redis.sentinel.password:哨兵节点自身开启认证时的密码,只有当哨兵的requirepass被设置时才需要。

如果哨兵没有设置requirepass,只写spring.data.redis.password就可以。最常遇到的报错是NOAUTH Authentication required,明明密码没错,应用就是连不上,原因多半是把两个密码的位置搞反了。

5.3 故障转移时,应用侧发生了什么

这一点是很多人理解上的盲区。你配好了哨兵,以为主库挂了之后Spring Boot会自动切换,但Lettuce的行为并不是瞬间完成的。

当主库发生故障,哨兵集群会先做主观下线判定,再根据quorum做客观下线,然后触发故障转移。这个过程少说也要几秒钟。在这几秒内,应用连的还是旧主库地址,每次请求大概率报连接超时。

故障转移完成后,Lettuce连接工厂需要感知到“master地址变了”。Lettuce默认会在连接断开后重新向哨兵获取master地址,但如果你用的是旧连接池里的空闲连接,可能需要等到下个心跳周期才刷新。有一种办法是开启哨兵拓扑刷新配置,但不是所有版本都有友好的YAML参数。我在项目里是自定义一个监听器,定期调用哨兵的SENTINEL get-master-addr-by-name mymaster去确认当前主库地址,然后把旧连接工厂全部关闭重建。

简单做法是让应用监听RedisConnectionFailureException,捕获后主动调用:

StringRedisTemplate.getConnectionFactory().getConnection().close();

强制Lettuce丢弃旧连接,下次获取连接时重新走哨兵查询流程。这个方法不太优雅,但关键时刻能救急。

5.4 只配一个哨兵地址的隐患

很多项目在配置nodes时只写了一个哨兵节点。单点故障风险很大:如果这个哨兵挂了,应用就不知道该找谁问master地址了。

哨兵的意义就是通过多个节点达成共识,所以nodes最少写三个。开发环境可以只有一个,但生产环境一定不要省。

另外特别注意:哨兵模式要求客户端连接的Redis数据库index为0。如果设置spring.data.redis.database为非0值,某些版本的Lettuce在切换master时会出问题。因为Redis哨兵模式下通常只用db0,我建议这个字段保持默认。


6. 集群模式配置:16384个槽位和MOVED重定向

当单机内存不够、写入吞吐量需求远超单实例能力时,就该考虑集群模式。Redis集群采用无中心化架构,数据按key的哈希结果散列到16384个槽中,每个主节点负责一部分槽位,同时每个主节点可以挂一个或多个从节点。

6.1 集群节点规划

生产环境最常见的最小集群是6个节点:3个主节点、3个从节点。每个主节点负责约5461个槽位,从节点分别做备份。

搭建测试集群可以任意节点数,我一般会在本机起6个端口:7000、7001、7002作为主,7003、7004、7005作为各自的从节点。用redis-cli --cluster create一条命令就能组起来:

redis-cli --cluster create \ 192.168.1.20:7000 192.168.1.21:7000 192.168.1.22:7000 \ 192.168.1.23:7000 192.168.1.24:7000 192.168.1.25:7000 \ --cluster-replicas 1

每个节点的redis.conf至少要开启:

cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000

6.2 Spring Boot集群配置

Spring Boot连接集群,不需要列出所有节点,只要给出一组“种子节点”即可。客户端连上其中一个,就能通过CLUSTER命令获取整个集群的拓扑。不过为了冗余,我一般写三个种子节点。

spring: data: redis: password: cluster-pass cluster: nodes: - 192.168.1.20:7000 - 192.168.1.21:7000 - 192.168.1.22:7000 max-redirects: 3 timeout: 3s lettuce: pool: enabled: true max-active: 16 max-idle: 8

max-redirects表示客户端在遇到MOVED重定向时最多追踪几次。默认是5,配成3一般够用。如果集群拓扑变化频繁,可以稍微调大。

集群模式下没有database概念,只有db0。配置了非0的database,客户端要么启动报错,要么直接忽略,Redis集群本身就不支持多逻辑库,这一点要格外提醒。

6.3 Lettuce处理MOVED/ASK的机制

Redis Cluster中,客户端写一个key到任何节点,如果该key所对应的槽不在当前节点,节点会返回MOVED错误,并给出正确的目标节点地址。Lettuce收到MOVED后会自动重定向到目标节点,并更新内存中的槽位映射。

大多数情况下,你不需要自己处理MOVED。但有一个场景会让Lettuce吃大亏:使用pipeline或事务时,一批命令可能在同一个pipeline里跨槽位执行,中间有一个命令触发MOVED,整批提交就可能失败。

我还在生产环境碰到过Lettuce的拓扑刷新问题。集群里有一个从节点被手动切换成主节点后,Lettuce并没有及时更新槽位映射,好几个key的请求持续打到旧节点,返回MOVED后虽然能跟随重定向,但每次都多一次网络往返,延迟明显变高。

解决办法是开启Lettuce的拓扑周期刷新。在配置类里手动构建连接工厂:

@Configuration public class RedisClusterConfig { @Bean public RedisConnectionFactory lettuceClusterConnectionFactory() { RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(); clusterConfig.clusterNode("192.168.1.20", 7000); clusterConfig.clusterNode("192.168.1.21", 7000); clusterConfig.clusterNode("192.168.1.22", 7000); clusterConfig.setPassword("cluster-pass"); ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(java.time.Duration.ofSeconds(30)) .enableAllAdaptiveRefreshTriggers() .build(); ClusterClientOptions clientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(topologyRefreshOptions) .build(); LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .clientOptions(clientOptions) .build(); return new LettuceConnectionFactory(clusterConfig, clientConfig); } }

这段配置的作用是:每30秒自动向集群要一次拓扑信息,同时在节点故障、MOVED过多等触发条件下刷新。生产环境建议至少开周期刷新。

6.4 集群模式下命令限制:MGET、pipeline、事务、Lua

这一节是集群模式最常见的业务改造点。单机模式下随便用的命令,在集群里不一定能用。

  • MGET/MSET:如果多个key不在同一个槽,执行失败。需要用哈希标签{user:123}把相关key固定到同一槽,或者改成循环单key操作。
  • pipeline:一批命令跨多个节点时,Lettuce会自动拆分成多个节点pipeline,但一致性没法保障。
  • MULTI/EXEC事务:只支持同一个节点上的key。跨节点事务是做不到的,集群本身就不支持跨节点原子性。
  • Lua脚本:脚本里访问的所有key必须落在同一个槽,否则报CROSSSLOT Keys in request don't hash to the same slot。

我见过一个项目从单机迁到集群后,所有缓存查询都报错,最后发现是代码里有一个地方在循环里调MGET传了5个key。改成循环单个GET之后性能反而没降多少,因为网络开销本来也没想象中那么大,但代码可读性差了很多。更稳妥的做法是使用哈希标签,把这几个key固定到同一个key所在槽:

user:123:profile user:123:orders

这两个key前面部分user:123用花括号包起来,比如{user:123}:profile和{user:123}:orders,就能保证在同一个槽。Redis只对花括号内的内容做哈希计算。

6.5 Jedis还是Lettuce:我的切身体会

如果你使用Redis Cluster,并且业务中大量使用事务、Lua、复杂pipeline,Lettuce的自动拓扑刷新机制虽然先进,但它对连接共享的控制更复杂,出问题后排查难度更高。Jedis的行为直白,每次从连接池拿连接、执行完归还,Cluster模式下同样能自动处理MOVED,缺点是要维护连接池参数,没有Lettuce省心。

我个人的建议是:

  • 用Spring Boot 3.x + Lettuce,能覆盖90%的业务场景。
  • 如果你的项目高度依赖Lua脚本和跨槽位操作,先用哈希标签解决槽位问题,不要急着换Jedis。
  • 如果确认要换Jedis,保留一份Lettuce的配置在注释里,方便回溯对比。

7. 横评四种模式的实测结论与避坑清单

文章写到这里,四种模式的配置基本都过了一遍。这一节我直接分享一组在模拟项目里压测得到的结论,以及一份踩坑清单。测试环境是虚拟机里四个Redis实例,项目是一个简单的订单缓存模拟项目X,压测工具是个人脚本,数据仅供横向参考。

7.1 四种模式在同一台机器上的表现对比

模式单次GET平均延迟单次SET平均延迟主库故障恢复时间配置复杂度
单机0.4ms0.5ms需要人工介入低
主从复制0.5ms(从库读)0.5ms需要人工介入中
哨兵0.6ms0.6ms5~10秒自动切换中高
集群0.7ms0.8ms秒级感知+手动确认高

单机和主从复制在低并发下差距很小,但主从复制在读取压力大时可以横向扩展从库,这是单机无法做到的。哨兵模式因为多了一层查询开销,比直接连主库稍慢,但这个延迟在可接受范围内。集群模式因为涉及槽位计算和可能的重定向,单次延迟稍微高一点,不过换来的是横向扩容能力和更高的总吞吐量。

我压测时发现一个有意思的现象:在并发只有200左右的场景下,集群模式反而比单机模式慢,原因是每次命令都要做CRC16哈希计算,Lettuce还要维护槽位路由表,这些都是额外开销。但并发到2000以上时,单机开始出现CPU尖刺和连接排队,集群的吞吐优势就出来了。所以选型真不是越高级越好,你的业务并发和容量要匹配。

7.2 我从这些配置里总结出的避坑清单

把四种模式遇到的典型问题汇总成一张表,每次配置完可以对照排查:

问题现象可能原因解决思路
配置了单机地址,但连接的是其他模式的节点配置文件残留cluster.nodes或sentinel.nodes删除无关配置,只保留当前模式字段
启动报NOAUTH Authentication required密码配置只写一个,或者写错字段区分数据节点密码和哨兵节点密码
主从复制正常,但从库读不到新数据主从复制延迟,或从库没有配置masterauth检查info replication偏移量,确认主从认证
哨兵模式下主库切换后应用仍报连接超时旧连接未及时刷新开启拓扑周期刷新或捕获异常后重建连接工厂
集群模式运行一段时间后部分key读写变慢客户端槽位映射过期检查Lettuce拓扑刷新配置,考虑缩短刷新周期
集群模式下执行MGET报CROSSSLOT目标key散落到不同槽位使用哈希标签重新设计key
RedisTemplate存储的内容在命令行显示乱码默认JDK序列化导致自定义RedisTemplate,改用String和JSON序列化
Spring Boot 3.x项目配置spring.redis不生效配置前缀已变成spring.data.redis统一使用新前缀

7.3 从零到生产的一条稳妥升级路径

如果你现在还在跑单机,想往高可用方向走,我不建议一步跳到集群。更稳妥的路径是:

  1. 先把单机模式跑通,包括连接池、序列化、异常处理,确保应用层没有任何Redis连接问题。
  2. 加一台从库,做纯手动的主从复制,把主从延迟摸清楚。
  3. 再上哨兵,验证故障转移后应用能否自动恢复。
  4. 最后,当数据量或并发量明确超过单机能力时,再迁移到集群。

整个过程中,Spring Boot的RedisConnectionFactory接口是不变的,业务代码应尽量依赖RedisTemplate而不是具体连接工厂。这么一来,模式升级时通常只需要改YAML和连接工厂配置类,不需要大规模改业务代码。

另外建议把每种模式的配置文件放在Git仓库的不同目录里,比如config/single、config/sentinel、config/cluster,启动时通过--spring.config.additional-location指定。少了手动改配置的环节,也避免配置串味。

我在这个项目里踩掉最多的坑,就是配置残留和密码分离这两个问题。这两种问题都不会导致启动立刻失败,而是在运行一段时间后才爆发,所以每次上线前,我会在部署环境里跑一个30分钟的稳定性测试,专门观察连接池、慢查询和异常日志。等你把四种模式都亲手配一遍,再回头看一开始那些复杂报错,会发现它们其实都指向同一个点:客户端连接工厂没有拿到它所期望的那一套配置。

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

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

立即咨询