Spring Boot配置Redis主从复制

先说结论,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);
    }
}

你可能感兴趣的:(数据库,redis,spring,boot)