java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot 3整合Redis三种模式

SpringBoot 3整合Redis三种模式(单机/哨兵/集群)的实战指南

作者:Mr.4567

在实际项目中,Redis的部署模式可能因环境而异:开发环境用单机,测试/生产用哨兵或集群,如果每次切换环境都要改代码,那简直是灾难,本文基于 SpringBoot 3 + Lettuce,通过一份配置文件的 mode 字段,动态选择单机、哨兵、集群三种连接模式,需要的朋友可以参考下

在实际项目中,Redis 的部署模式可能因环境而异:开发环境用单机,测试/生产用哨兵或集群。如果每次切换环境都要改代码,那简直是灾难。本文基于 SpringBoot 3 + Lettuce,通过一份配置文件的 mode 字段,动态选择单机、哨兵、集群三种连接模式,代码简洁、扩展性强,可直接用于生产。

一、前言

SpringBoot 3 对 Redis 的自动配置做了不少优化,但自动配置只支持单一模式(默认读取 spring.data.redis.*)。当你需要同时支持单机/哨兵/集群并在不同环境灵活切换时,手动配置 RedisConnectionFactory 才是最优解。

本文的源码来自真实项目,已在 SpringBoot 3.x 环境验证,具备以下特点:

二、依赖引入

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
);

如果你不涉及复杂对象存储,可以考虑用 GenericJackson2JsonRedisSerializer 简化配置。

4. RedisTemplate 的 key/value 序列化策略

序列化器适用位置原因
StringRedisSerializerkey、hashKey保证 Redis 中 key 可读,便于运维排查
Jackson2JsonRedisSerializervalue、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 单机 / 哨兵 / 集群三种模式的动态切换,核心要点:

  1. 用 mode 字段做开关,代码分支清晰,零侵入切换环境。
  2. 手动注册 RedisConnectionFactory,绕过 SpringBoot 自动配置,灵活可控。
  3. 统一序列化策略:key 用 String,value 用 Jackson,兼顾可读性与复杂对象支持。
  4. 配置分离:三种模式的节点信息放在不同配置节,多环境拆 profile 即可。

这套方案已在多个生产项目中稳定运行,希望能帮到正在做 Redis 多模式适配的你。

以上就是SpringBoot 3整合Redis三种模式(单机/哨兵/集群)的实战指南的详细内容,更多关于SpringBoot 3整合Redis三种模式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文