Spring Boot集成Redis:单机、哨兵、集群三种模式统一配置实战

一、前言

在微服务架构中,Redis作为高性能的缓存数据库被广泛应用。Spring Boot提供了spring-boot-starter-data-redis来简化Redis的集成,但在实际项目中,我们往往需要根据不同的环境(开发、测试、生产)切换不同的Redis部署模式(单机、哨兵、集群)。

本文将详细介绍如何在Spring Boot中实现Redis三种模式的统一配置,通过一个配置参数即可灵活切换,大大提高项目的可维护性和灵活性。

二、技术栈

  • Spring Boot 2.x/3.x

  • Redis 6.x/7.x

  • Lettuce连接池

  • Jackson2Json序列化

三、项目依赖

首先在pom.xml中添加Redis相关依赖:

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

四、配置文件设计

4.1 application.yml 完整配置

XML 复制代码
spring:
  data:
    redis:
      # Redis 运行模式:standalone(单机),sentinel(哨兵),cluster(集群)
      mode: standalone
      # 数据库索引(0-15)
      database: 0
      # 连接超时时间(毫秒)
      timeout: 60000
      # Redis 服务器密码
      password: your_password
      
      # 连接池配置
      lettuce:
        pool:
          max-active: 8      # 最大连接数
          max-idle: 8        # 最大空闲连接
          min-idle: 0        # 最小空闲连接
          max-wait: -1ms     # 连接池满时等待时间,-1表示一直等待
      
      # 单机模式配置
      host: 127.0.0.1
      port: 6379
      
      # 哨兵模式配置
      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    # 最大重定向次数

五、核心代码实现

5.1 配置属性类

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.List;

@Data
@Component
@ConfigurationProperties(prefix = "spring.data.redis")
public class RedisProperties {
    
    // 通用配置
    private String mode = "standalone";
    private int database = 0;
    private Duration timeout = Duration.ofSeconds(60);
    private String password;
    
    // 单机模式配置
    private String host = "127.0.0.1";
    private int port = 6379;
    
    // 哨兵模式配置
    private Sentinel sentinel = new Sentinel();
    
    // 集群模式配置
    private Cluster cluster = new Cluster();
    
    // Lettuce连接池配置
    private Lettuce lettuce = new Lettuce();
    
    @Data
    public static class Sentinel {
        private String master;
        private List<String> nodes;
    }
    
    @Data
    public static class Cluster {
        private List<String> nodes;
        private int maxRedirects = 3;
    }
    
    @Data
    public static class Lettuce {
        private Pool pool = new Pool();
        
        @Data
        public static class Pool {
            private int maxActive = 8;
            private int maxIdle = 8;
            private int minIdle = 0;
            private Duration maxWait = Duration.ofMillis(-1);
        }
    }
}

5.2 统一Redis配置类

java 复制代码
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.List;

@Slf4j
@Configuration
public class UnifiedRedisConfig {
    
    @Autowired
    private RedisProperties redisProperties;
    
    /**
     * 创建 Redis 连接工厂(统一入口)
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        String mode = redisProperties.getMode().toLowerCase();
        log.info("初始化Redis连接工厂,当前模式: {}", mode);
        
        RedisConnectionFactory connectionFactory;
        
        switch (mode) {
            case "sentinel":
                connectionFactory = createSentinelConnectionFactory();
                break;
            case "cluster":
                connectionFactory = createClusterConnectionFactory();
                break;
            case "standalone":
            default:
                connectionFactory = createStandaloneConnectionFactory();
                break;
        }
        
        log.info("Redis连接工厂初始化完成");
        return connectionFactory;
    }
    
    /**
     * 创建单机模式连接工厂
     */
    private RedisConnectionFactory createStandaloneConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(redisProperties.getHost());
        config.setPort(redisProperties.getPort());
        config.setDatabase(redisProperties.getDatabase());
        
        if (StringUtils.hasText(redisProperties.getPassword())) {
            config.setPassword(redisProperties.getPassword());
        }
        
        return createLettuceConnectionFactory(config);
    }
    
    /**
     * 创建哨兵模式连接工厂
     */
    private RedisConnectionFactory createSentinelConnectionFactory() {
        RedisSentinelConfiguration config = new RedisSentinelConfiguration();
        config.master(redisProperties.getSentinel().getMaster());
        config.setDatabase(redisProperties.getDatabase());
        
        if (StringUtils.hasText(redisProperties.getPassword())) {
            config.setPassword(redisProperties.getPassword());
        }
        
        // 配置哨兵节点
        List<String> sentinelNodes = redisProperties.getSentinel().getNodes();
        if (sentinelNodes != null && !sentinelNodes.isEmpty()) {
            for (String node : sentinelNodes) {
                String[] parts = node.split(":");
                if (parts.length == 2) {
                    config.sentinel(parts[0], Integer.parseInt(parts[1]));
                } else {
                    log.warn("哨兵节点格式错误,应为 host:port,实际: {}", node);
                }
            }
        }
        
        return createLettuceConnectionFactory(config);
    }
    
    /**
     * 创建集群模式连接工厂
     */
    private RedisConnectionFactory createClusterConnectionFactory() {
        RedisClusterConfiguration config = new RedisClusterConfiguration();
        config.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
        
        if (StringUtils.hasText(redisProperties.getPassword())) {
            config.setPassword(redisProperties.getPassword());
        }
        
        // 配置集群节点
        List<String> clusterNodes = redisProperties.getCluster().getNodes();
        if (clusterNodes != null && !clusterNodes.isEmpty()) {
            for (String node : clusterNodes) {
                String[] parts = node.split(":");
                if (parts.length == 2) {
                    config.clusterNode(parts[0], Integer.parseInt(parts[1]));
                } else {
                    log.warn("集群节点格式错误,应为 host:port,实际: {}", node);
                }
            }
        }
        
        return createLettuceConnectionFactory(config);
    }
    
    /**
     * 创建Lettuce连接工厂(通用方法)
     */
    private LettuceConnectionFactory createLettuceConnectionFactory(Object redisConfig) {
        LettuceClientConfiguration clientConfig = createLettuceClientConfiguration();
        
        if (redisConfig instanceof RedisStandaloneConfiguration) {
            return new LettuceConnectionFactory((RedisStandaloneConfiguration) redisConfig, clientConfig);
        } else if (redisConfig instanceof RedisSentinelConfiguration) {
            return new LettuceConnectionFactory((RedisSentinelConfiguration) redisConfig, clientConfig);
        } else if (redisConfig instanceof RedisClusterConfiguration) {
            return new LettuceConnectionFactory((RedisClusterConfiguration) redisConfig, clientConfig);
        } else {
            throw new IllegalArgumentException("不支持的Redis配置类型");
        }
    }
    
    /**
     * 创建Lettuce客户端配置(包含连接池和超时配置)
     */
    private LettuceClientConfiguration createLettuceClientConfiguration() {
        GenericObjectPoolConfig<?> poolConfig = createPoolConfig();
        
        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = 
            LettucePoolingClientConfiguration.builder()
                .poolConfig(poolConfig);
        
        // 设置命令超时时间
        if (redisProperties.getTimeout() != null) {
            builder.commandTimeout(redisProperties.getTimeout());
        }
        
        // 集群模式特殊配置:启用集群拓扑刷新
        if ("cluster".equalsIgnoreCase(redisProperties.getMode())) {
            builder.clientOptions(io.lettuce.core.cluster.ClusterClientOptions.builder()
                .topologyRefreshOptions(
                    io.lettuce.core.cluster.ClusterTopologyRefreshOptions.builder()
                        .enableAdaptiveRefresh(true)      // 启用自适应刷新
                        .enablePeriodicRefresh(Duration.ofSeconds(60))  // 周期性刷新
                        .refreshPeriod(Duration.ofSeconds(60))
                        .build()
                )
                .build());
            log.info("集群模式已启用拓扑自动刷新");
        }
        
        return builder.build();
    }
    
    /**
     * 创建连接池配置
     */
    private GenericObjectPoolConfig<?> createPoolConfig() {
        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        RedisProperties.Lettuce.Pool pool = redisProperties.getLettuce().getPool();
        
        poolConfig.setMaxTotal(pool.getMaxActive());
        poolConfig.setMaxIdle(pool.getMaxIdle());
        poolConfig.setMinIdle(pool.getMinIdle());
        poolConfig.setMaxWait(pool.getMaxWait());
        
        // 连接池优化配置
        poolConfig.setTestOnBorrow(true);     // 获取连接时测试连接可用性
        poolConfig.setTestOnReturn(false);
        poolConfig.setTestWhileIdle(true);     // 空闲时测试连接
        poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(30)); // 空闲连接检测周期
        
        return poolConfig;
    }
    
    /**
     * 创建 RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        
        // 使用 StringRedisSerializer 序列化 key
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        
        // 使用 Jackson2JsonRedisSerializer 序列化 value
        GenericJackson2JsonRedisSerializer jacksonSerializer = getJacksonSerializer();
        template.setValueSerializer(jacksonSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
    
    /**
     * 创建并配置 Jackson2JsonRedisSerializer
     */
    private GenericJackson2JsonRedisSerializer 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 GenericJackson2JsonRedisSerializer(objectMapper);
    }
}

5.3 Redis健康检查(可选)

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisHealthChecker {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @PostConstruct
    public void init() {
        checkRedisConnection();
    }
    
    /**
     * 检查Redis连接状态
     */
    public boolean checkRedisConnection() {
        try {
            RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            String pong = connection.ping();
            connection.close();
            
            if ("PONG".equals(pong)) {
                log.info("Redis连接成功");
                return true;
            } else {
                log.error("Redis连接失败");
                return false;
            }
        } catch (Exception e) {
            log.error("Redis连接异常: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 定期健康检查
     */
    @org.springframework.scheduling.annotation.Scheduled(fixedDelay = 30, timeUnit = TimeUnit.SECONDS)
    public void scheduledHealthCheck() {
        checkRedisConnection();
    }
}

六、使用示例

6.1 基础操作Service

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class RedisService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 设置值
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 设置值并指定过期时间
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
    
    /**
     * 获取值
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    /**
     * 删除key
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }
    
    /**
     * 判断key是否存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
    
    /**
     * 设置过期时间
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }
}
相关推荐
杨运交14 小时前
[041][公共模块]分布式唯一ID生成器设计与实现:一款灵活可扩展的雪花算法框架
spring boot
用户3074596982071 天前
Redis 延时队列详解
redis
烤代码的吐司君1 天前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
Flittly2 天前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
Flynt2 天前
从Spring Boot 4.0升到4.1,我在Maven和gRPC上栽了跟头
java·spring boot·后端
掉鱼的猫4 天前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·spring boot
leeyi4 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
人活一口气4 天前
Spring Boot与AIGC的完美结合:从零搭建智能内容生成平台
java·spring boot·aigc
云技纵横5 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy5 天前
用Claude快速解决Redis代码报错反复无解的问题
redis