一、前言
在微服务架构中,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);
}
}