Redis哨兵的配置,参考我这篇文章:
1.背景
网上搜半天没搜到份好用的,自己整理了下方便以后复制,基于springboot 2.6.13。
- lettuce
- commons-pool2
2.集成
2.1 导入pom
<!-- spring-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.2 编写配置文件
结合自己的项目更新,此处直接写在了
application.yml
中。
spring:
redis:
password: 主节点密码
lettuce:
pool:
# 最大连接数
max-active: 20
# 连接池中获取连接时最大等待时间ms
max-wait: 300
# 最大空闲连接数
max-idle: 5
# 最小空闲连接数
min-idle: 1
sentinel:
master: 要监控的主节点
password: 哨兵密码
# 哨兵集群
nodes:
- ip1:26379
- ip2:26380
- ip3:26381
2.3 编写配置实体类
RedisProperties:映射配置文件
package cn.yang37.za.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Set;
/**
* @description:
* @class: RedisProperties
* @author: [email protected]
* @date: 2024/6/7 16:07
* @version: 1.0
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String password;
private Lettuce lettuce;
private Sentinel sentinel;
@Data
public static class Lettuce {
private Pool pool;
@Data
public static class Pool {
private int maxActive;
private int maxWait;
private int maxIdle;
private int minIdle;
}
}
@Data
public static class Sentinel {
private String master;
private String password;
private Set<String> nodes;
}
}
2.4 编写哨兵配置类
RedisSentinelConfig:哨兵配置信息加载、commonsPool配置信息加载、lettuce连接池构建
package cn.yang37.za.config;
import cn.yang37.za.properties.RedisProperties;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.resource.DefaultClientResources;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @class: RedisSentinelConfig
* @author: [email protected]
* @date: 2024/6/7 15:11
* @version: 1.0
*/
@Slf4j
@Configuration
public class RedisSentinelConfig {
@Resource
private RedisProperties redisProperties;
private DefaultClientResources clientResources;
/**
* 构建commonsPool配置
*
* @return .
*/
@Bean
public GenericObjectPoolConfig<?> genericObjectPoolConfig() {
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
config.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
config.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
config.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait());
return config;
}
/**
* 构建RedisSentinel配置
*
* @return .
*/
@Bean
public RedisSentinelConfiguration sentinelConfiguration() {
RedisSentinelConfiguration redisSentinelConfiguration =
new RedisSentinelConfiguration(redisProperties.getSentinel().getMaster(), redisProperties.getSentinel().getNodes());
// 主节点密码
redisSentinelConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
// 哨兵密码
redisSentinelConfiguration.setSentinelPassword(RedisPassword.of(redisProperties.getSentinel().getPassword().toCharArray()));
return redisSentinelConfiguration;
}
/**
* 构建lettuce连接池
*
* @param genericObjectPoolConfig .
* @param sentinelConfiguration .
* @return .
*/
@Bean
public RedisConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig<?> genericObjectPoolConfig,
RedisSentinelConfiguration sentinelConfiguration) {
// 声明资源
this.clientResources = DefaultClientResources.create();
// 构建lettuce配置
LettucePoolingClientConfiguration lettuceClientConfiguration =
LettucePoolingClientConfiguration.builder()
.poolConfig(genericObjectPoolConfig)
.readFrom(ReadFrom.REPLICA)
.clientResources(clientResources)
.build();
// 构建lettuce连接池
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(sentinelConfiguration, lettuceClientConfiguration);
// 每次获取连接时验证连接的有效性(注意性能影响)
lettuceConnectionFactory.setValidateConnection(true);
// 手动触发连接池初始化
lettuceConnectionFactory.afterPropertiesSet();
log.info("connected to redis sentinel,node info: {}", sentinelConfiguration.getSentinels());
return lettuceConnectionFactory;
}
/**
* 应用关闭时触发
*/
@PreDestroy
public void shutdown() {
if (null != clientResources) {
clientResources.shutdown(100, 100, TimeUnit.MILLISECONDS);
}
}
}
注意,读取的策略根据ReadFrom
来配置。
// 构建lettuce配置
LettucePoolingClientConfiguration lettuceClientConfiguration =
LettucePoolingClientConfiguration.builder()
.poolConfig(genericObjectPoolConfig)
// 确定从哪个节点读取数据
.readFrom(ReadFrom.REPLICA)
.clientResources(clientResources)
.build();
策略 | 用途 |
---|---|
MASTER / UPSTREAM | 从主节点读取数据。主节点通常处理所有写操作。 |
MASTER_PREFERRED / UPSTREAM_PREFERRED | 优先从主节点读取数据,但如果主节点不可用,则从从节点读取数据。 |
REPLICA_PREFERRED | 优先从从节点读取数据,但如果所有从节点不可用,则从主节点读取数据。 |
REPLICA | 仅从从节点读取数据。 |
SLAVE (Deprecated) | 仅从从节点读取数据。已废弃,用 REPLICA 替代。 |
LOWEST_LATENCY | 从具有最低延迟的节点读取数据,可能是主节点或从节点。 |
NEAREST (Deprecated) | 从最近的节点读取数据。已废弃,通常 LOWEST_LATENCY 替代。 |
ANY | 从任意节点读取数据。通常用于负载均衡。 |
ANY_REPLICA | 从任意从节点读取数据。用于从节点负载均衡,但不包括主节点。 |
2.5 编写Redis配置类
RedisConfig:自定义RedisTemplate、StringRedisTemplate
package cn.yang37.za.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @description:
* @class: RedisConfig
* @author: [email protected]
* @date: 2024/5/27 11:40
* @version: 1.0
*/
@Configuration
public class RedisConfig {
/**
* 1.使用自定义lettuce连接池
* 2.声明序列化方式
*
* @param connectionFactory .
* @return .
*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 配置String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 配置Json的序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// Key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
// Hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// Value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// Hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 1.使用自定义lettuce连接池
* 2.声明序列化方式
*
* @param connectionFactory .
* @return .
*/
@Bean
public StringRedisTemplate stringRedisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory connectionFactory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
stringRedisTemplate.setConnectionFactory(connectionFactory);
stringRedisTemplate.setKeySerializer(stringRedisSerializer);
stringRedisTemplate.setValueSerializer(stringRedisSerializer);
stringRedisTemplate.afterPropertiesSet();
return stringRedisTemplate;
}
}
3.测试
导入test组件
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
编写RedisControl的测试用例
package cn.yang37.za.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Slf4j
@SpringBootTest
class RedisControllerTest {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
void name1() {
final String key = "yang37";
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "123", 600, TimeUnit.SECONDS);
log.info("flag: {}", flag);
String value = stringRedisTemplate.opsForValue().get(key);
log.info("value: {}", value);
Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
log.info("expire: {}", expire);
}
}
运行
4.注意
哨兵的配置文件
中,填写你程序能访问到的ip,似乎Redis是根据这里填写的ip值返回的节点地址。
截图中,哨兵配置文件不是redis自动追加了几行嘛,我框起来的那里。
sentinel known-replica mymaster xx
这个配置文件,估计就是哨兵服务自己为了持久化信息保存的,它内存里面估计就是读取known-replica的ip值来返回。
我服务器上自己做测试用的,开始一直写的127.0.0.1
,SpringBoot中就一直跑去访问127.0.0.1,改成实际的就好了。