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);
    }
}
相关推荐
lay_liu2 小时前
Spring Boot 自动配置
java·spring boot·后端
q5431470874 小时前
Redis Desktop Manager(Redis可视化工具)安装及使用详细教程
redis·git·bootstrap
anzhxu4 小时前
SpringBoot 3.x 整合swagger
java·spring boot·后端
小江的记录本4 小时前
【Bean】JavaBean(原生规范)/ Spring Bean 【重点】/ 企业级Bean(EJB/Jakarta Bean)
java·数据库·spring boot·后端·spring·spring cloud·mybatis
中国胖子风清扬4 小时前
Camunda 8 概念详解:梳理新一代工作流引擎的核心概念与组件
java·spring boot·后端·spring cloud·ai·云原生·spring webflux
gechunlian884 小时前
redis exporter手册
数据库·redis·缓存
yhole4 小时前
Spring Boot整合Redisson的两种方式
java·spring boot·后端
sthnyph4 小时前
Spring Boot 集成 Kettle
java·spring boot·后端
小龙报5 小时前
【数据结构与算法】栈和队列的综合应用:1.用栈实现队列 2.用队列实现栈 3.设计循环队列
c语言·数据结构·数据库·c++·redis·算法·缓存