先说结论,Spring Boot是不能直接通过application配置来实现主从Redis的配置的。Spring Boot支持的cluster是原始的使用槽的集群模式,而不是常用的主从集群,因此最好自己来搭建Redis的主从集群模式。
需要我们通过自己定义的方式来实现。
这里我们用docker来搭建redis集群,详细的docker-file如下:
version: "2.0"
services:
master:
image: redis
restart: always
container_name: master
ports:
- "6379:6379"
slave1:
image: redis
command: redis-server --slaveof master 6379
container_name: slave1
depends_on:
- master
ports:
- "6380:6379"
links:
- master:master
slave2:
image: redis
command: redis-server --slaveof master 6379
container_name: slave2
depends_on:
- master
ports:
- "6381:6379"
links:
- master:master
这里我们指定了一个master和2个slave,实际只需要使用一个slave即可
由于Redis操作的大部分是String
类型的,因此我们可以直接使用Spring中的StringRedisTemplate
这个类进行使用。
而这个类在Spring源码中的默认注入方法为
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
// 只有当没有注入时才会使用这个类
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
可以看到如果我们注入了自定义的StringRedisTemplate之后,就会以为以我们自己实现的类优先
因此我们可以考虑直接使用自己定义类注入的方式来实现。具体配置类如下
@Configuration
public class RedisConfiguration {
// 虚拟机的主机名
private String host = "dennis-1";
private int masterPort = 6379;
private int slavePort = 6380;
/**
* 自定义redisTemplate方法
*/
@Bean(name = "slave")
public StringRedisTemplate slaveRedisTemplate() {
StringRedisTemplate template = new StringRedisTemplate();
// 配置连接工厂
template.setConnectionFactory(factoryConfiguration(host, slavePort));
// 必须要有这一步执行配置的初始化
template.afterPropertiesSet();
return template;
}
@Bean(name = "master")
public StringRedisTemplate masterRedisTemplate() {
StringRedisTemplate template = new StringRedisTemplate();
// 配置连接工厂
template.setConnectionFactory(factoryConfiguration(host, masterPort));
// 必须要有这一步执行配置的初始化
template.afterPropertiesSet();
return template;
}
private RedisConnectionFactory factoryConfiguration(String host, int port) {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
// 可以采用Jedis或者Lettuce两种方式,Spring默认使用的是Lettuce
// 如果使用Jedis还需要自己引入jedis的依赖包
return new JedisConnectionFactory(configuration);
}
}
注意这里的方法
template.afterPropertiesSet();
每当我们重新改变了template的配置后,一定要执行这个方法,否则我们的设置不会生效
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
// 关键是这一步,会执行脚本初始化器
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<>(this);
}
initialized = true;
}
配置完成后,可以编写我们的Redis工具类,写操作由master负责,读操作由slave负责
@Slf4j
@Component
public class RedisUtils {
// 根据变量名称自动注入,或者使用@Resource注解或者添加@Qulifier注解
@Autowired
private StringRedisTemplate master, slave;
public void set(String key, Object value) {
master.opsForValue().set(key, String.valueOf(value));
}
public void set(String key, Object value, long seconds) {
master.opsForValue().set(key, String.valueOf(value), seconds, TimeUnit.SECONDS);
}
public void hset(String key, Map<String, String> map) {
master.opsForHash().putAll(key, map);
}
public void hset(String key, String field, Object value) {
master.opsForHash().put(key, field, value);
}
public void setex(String key, Object value, long seconds) {
while (!master.opsForValue().setIfAbsent(key, String.valueOf(value), seconds, TimeUnit.SECONDS)) {
}
}
public String get(String key) {
return slave.opsForValue().get(key);
}
public Map<Object, Object> hget(String key) {
return slave.opsForHash().entries(key);
}
public String hget(String key, String field) {
return (String) slave.opsForHash().get(key, field);
}
public boolean hasKey(String key) {
try {
return slave.hasKey(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
public int llen(String key) {
return Math.toIntExact(slave.opsForList().size(key));
}
public void lpush(String key, Object value) {
master.opsForList().leftPush(key, String.valueOf(value));
}
public String rpop(String key) {
return master.opsForList().rightPop(key);
}
public List<String> getValues(String key) {
return slave.opsForList().range(key, 0, -1);
}
public void pipelined(RedisCallback<Object> redisCallback) {
master.executePipelined(redisCallback);
}
public List<Object> pipelinedGet(RedisCallback<Object> redisCallback) {
return slave.executePipelined(redisCallback);
}
public void delete(String item) {
master.delete(item);
}
}