在实际项目中,redis 的部署模式可能因环境而异:开发环境用单机,测试/生产用哨兵或集群。如果每次切换环境都要改代码,那简直是灾难。本文基于 springboot 3 + lettuce,通过一份配置文件的 mode 字段,动态选择单机、哨兵、集群三种连接模式,代码简洁、扩展性强,可直接用于生产。
一、前言
springboot 3 对 redis 的自动配置做了不少优化,但自动配置只支持单一模式(默认读取 spring.data.redis.*)。当你需要同时支持单机/哨兵/集群并在不同环境灵活切换时,手动配置 redisconnectionfactory 才是最优解。
本文的源码来自真实项目,已在 springboot 3.x 环境验证,具备以下特点:
- ✅ 一份配置文件,通过
mode字段切换三种模式 - ✅ 使用 lettuce 作为底层客户端(springboot 3 默认)
- ✅ 统一 jackson2jsonredisserializer 序列化,支持复杂对象
- ✅ 密码、数据库、连接池等公共配置统一管理
- ✅ 代码结构清晰,便于维护和扩展
二、依赖引入
springboot 3 中,spring-boot-starter-data-redis 默认使用 lettuce,无需额外引入 jedis。
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
<!-- lettuce 连接池需要 commons-pool2 -->
<dependency>
<groupid>org.apache.commons</groupid>
<artifactid>commons-pool2</artifactid>
</dependency>
<!-- jackson 序列化 -->
<dependency>
<groupid>com.fasterxml.jackson.core</groupid>
<artifactid>jackson-databind</artifactid>
</dependency>注意:如果不引入 commons-pool2,连接池配置(lettuce.pool.*)不会生效。
三、配置文件设计
application.yml 中通过 spring.data.redis.mode 控制模式,值分别为 standalone、sentinel、cluster。
spring:
data:
redis:
# redis 运行模式: standalone(单机), sentinel(哨兵), cluster(集群)
mode: standalone
# 单机模式配置
host: 172.16.18.53
port: 6379
# 通用配置
database: 0
timeout: 60000
password: dkdh@13579
# 连接池配置
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
# 哨兵模式配置
sentinel:
master: mymaster
nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381
# 集群模式配置
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设计思路:
| 配置项 | 说明 |
|---|---|
mode | 核心开关,决定走哪个分支 |
host/port | 仅单机模式使用 |
sentinel.master/nodes | 仅哨兵模式使用 |
cluster.nodes/max-redirects | 仅集群模式使用 |
password/database/timeout | 三种模式通用 |
这样即使同一套代码,只需要在 application-dev.yml、application-prod.yml 中修改 mode 和对应节点即可,无需改一行 java 代码。
四、核心配置类
完整代码如下(来自真实项目,可直接复制使用):
@configuration
public class redisconfig {
@value("${spring.data.redis.mode}")
private string redismode;
@value("${spring.data.redis.host}")
private string host;
@value("${spring.data.redis.port}")
private int port;
@value("${spring.data.redis.password}")
private string password;
@value("${spring.data.redis.database}")
private int database;
@value("${spring.data.redis.sentinel.master}")
private string sentinelmaster;
@value("${spring.data.redis.sentinel.nodes}")
private string sentinelnodes;
@value("${spring.data.redis.cluster.nodes}")
private string clusternodes;
@value("${spring.data.redis.cluster.max-redirects}")
private int clustermaxredirects;
/**
* 创建 redis 连接工厂
*/
@bean
public redisconnectionfactory redisconnectionfactory() {
switch (redismode) {
case "standalone":
return createstandaloneconnectionfactory();
case "sentinel":
return createsentinelconnectionfactory();
case "cluster":
return createclusterconnectionfactory();
default:
throw new illegalargumentexception("invalid redis mode configuration: " + redismode);
}
}
/**
* 创建单机模式连接工厂
*/
private redisconnectionfactory createstandaloneconnectionfactory() {
redisstandaloneconfiguration config = new redisstandaloneconfiguration();
config.sethostname(host);
config.setport(port);
config.setpassword(password);
config.setdatabase(database);
return new lettuceconnectionfactory(config);
}
/**
* 创建哨兵模式连接工厂
*/
private redisconnectionfactory createsentinelconnectionfactory() {
redissentinelconfiguration config = new redissentinelconfiguration()
.master(sentinelmaster);
// 配置哨兵节点
for (string node : sentinelnodes.split(",")) {
string[] parts = node.split(":");
config.sentinel(parts[0], integer.parseint(parts[1]));
}
config.setpassword(password);
config.setdatabase(database);
return new lettuceconnectionfactory(config);
}
/**
* 创建集群模式连接工厂
*/
private redisconnectionfactory createclusterconnectionfactory() {
redisclusterconfiguration config = new redisclusterconfiguration();
// 配置集群节点
for (string node : clusternodes.split(",")) {
string[] parts = node.split(":");
config.clusternode(parts[0], integer.parseint(parts[1]));
}
config.setmaxredirects(clustermaxredirects);
config.setpassword(password);
return new lettuceconnectionfactory(config);
}
/**
* 创建 redistemplate
*/
@bean
public redistemplate<string, object> redistemplate(redisconnectionfactory redisconnectionfactory) {
redistemplate<string, object> template = new redistemplate<>();
template.setconnectionfactory(redisconnectionfactory);
// key 使用 string 序列化
template.setkeyserializer(new stringredisserializer());
template.sethashkeyserializer(new stringredisserializer());
// value 使用 jackson2jsonredisserializer
jackson2jsonredisserializer<object> jacksonserializer = getjacksonserializer();
template.setvalueserializer(jacksonserializer);
template.sethashvalueserializer(jacksonserializer);
template.afterpropertiesset();
return template;
}
/**
* 创建并配置 jackson2jsonredisserializer
*/
private jackson2jsonredisserializer<object> getjacksonserializer() {
objectmapper objectmapper = new objectmapper();
// 任何字段(包括私有字段)都序列化
objectmapper.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any);
// 启用默认类型信息,反序列化时能正确识别类型
objectmapper.activatedefaulttyping(
laissezfairesubtypevalidator.instance,
objectmapper.defaulttyping.non_final,
jsontypeinfo.as.property
);
return new jackson2jsonredisserializer<>(objectmapper, object.class);
}
}五、关键代码解析
1. 三种模式的配置对象差异
| 模式 | 配置类 | 特点 |
|---|---|---|
| 单机 | redisstandaloneconfiguration | 直连单个 host:port |
| 哨兵 | redissentinelconfiguration | 通过 master 名称 + 哨兵节点列表,客户端自动感知主从切换 |
| 集群 | redisclusterconfiguration | 需要配置所有 master 节点,支持 maxredirects |
2. 为什么不用 springboot 自动配置?
springboot 3 的 redisautoconfiguration 在检测到 redisconnectionfactory bean 后会自动退避。手动注册 redisconnectionfactory 后,自动配置不会覆盖,这正是我们实现多模式切换的关键。
3.jackson2jsonredisserializer的坑
objectmapper.activatedefaulttyping(
laissezfairesubtypevalidator.instance,
objectmapper.defaulttyping.non_final,
jsontypeinfo.as.property
);- 必须开启 defaulttyping:否则反序列化时会丢失具体类型,变成
linkedhashmap。 - laissezfairesubtypevalidator:宽松验证器,避免某些类被拒绝序列化。
- non_final:对所有非 final 类型保存类型信息,覆盖大部分 pojo。
- jsontypeinfo.as.property:类型信息作为 json 的一个属性(默认
@class),可读性好。
如果你不涉及复杂对象存储,可以考虑用 genericjackson2jsonredisserializer 简化配置。
4. redistemplate 的 key/value 序列化策略
| 序列化器 | 适用位置 | 原因 |
|---|---|---|
stringredisserializer | key、hashkey | 保证 redis 中 key 可读,便于运维排查 |
jackson2jsonredisserializer | value、hashvalue | 支持复杂对象,自动类型识别 |
六、多环境实践建议
在真实项目中,推荐按环境拆分配置文件:
application.yml # 公共配置 application-dev.yml # mode: standalone application-test.yml # mode: sentinel application-prod.yml # mode: cluster
application-prod.yml 示例:
spring:
data:
redis:
mode: cluster
password: ${redis_password}
cluster:
nodes: 10.0.0.1:7001,10.0.0.2:7002,10.0.0.3:7003
max-redirects: 3启动时通过 --spring.profiles.active=prod 切换,代码零改动。
七、常见问题排查
q1: 启动报错noauth authentication required
原因:集群/哨兵模式密码未设置。
解决:确保 config.setpassword(password) 被调用。注意集群模式下密码是设置在 redisclusterconfiguration 上,不是每个节点单独设置。
q2: 哨兵模式连接不上,日志显示连接的是127.0.0.1
原因:哨兵返回的 master 地址是内网 ip,客户端无法访问。
解决:在哨兵配置中设置 sentinel announce-ip 和 announce-port 为可访问的地址。
q3: 存进去的对象取出来变成linkedhashmap
原因:objectmapper 未开启 defaulttyping。
解决:参照本文 getjacksonserializer() 中的 activatedefaulttyping 配置。
q4: 连接池配置不生效
原因:未引入 commons-pool2 依赖。
解决:添加依赖即可,lettuce 会自动使用连接池。
q5: springboot 3 中redissentinelconfiguration.setpassword类型问题
原因:springboot 3 中 setpassword 参数是 redispassword 类型,但可以直接传 string(有重载)。
解决:本文写法 config.setpassword(password) 在 springboot 3.x 是兼容的。
八、扩展:连接池参数调优
lettuce 连接池配置建议:
lettuce:
pool:
max-active: 8 # 生产建议 16~32,视并发量而定
max-wait: -1 # 建议改为 2000ms,避免无限阻塞
max-idle: 8
min-idle: 2 # 保持一定数量空闲连接,避免冷启动慢
shutdown-timeout: 200ms如果你的业务是高并发场景,建议 max-active 调大,并把 max-wait 设置为合理超时(如 2000ms),防止请求堆积。
九、总结
本文通过一份 springboot 3 源码,实现了 redis 单机 / 哨兵 / 集群三种模式的动态切换,核心要点:
- 用
mode字段做开关,代码分支清晰,零侵入切换环境。 - 手动注册
redisconnectionfactory,绕过 springboot 自动配置,灵活可控。 - 统一序列化策略:key 用 string,value 用 jackson,兼顾可读性与复杂对象支持。
- 配置分离:三种模式的节点信息放在不同配置节,多环境拆 profile 即可。
这套方案已在多个生产项目中稳定运行,希望能帮到正在做 redis 多模式适配的你。
以上就是springboot 3整合redis三种模式(单机/哨兵/集群)的实战指南的详细内容,更多关于springboot 3整合redis三种模式的资料请关注代码网其它相关文章!
发表评论