SpringBoot集成Redis单节点和哨兵

1.背景

springboot使用redis单节点或者只使用哨兵、集群比较简单。yml配置即可,可以省去单独的连接池配置。实际项目中需要灵活切换单机或者哨兵模式,设计思路时如果配置了哨兵则连接池优先初始化哨兵工厂。反之使用单节点的默认redis配置即可。

2.pom.xml引入

<!-- Spring Boot Redis依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
	<version>2.1.5.RELEASE</version>
	<exclusions>
		<exclusion>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</exclusion>
		<exclusion>
			<groupId>io.lettuce</groupId>
			<artifactId>lettuce-core</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- 添加jedis客户端 -->
<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
</dependency>

2.yml配置

application.yml配置

spring:
  profiles:
    active: dev
  application:
    name: redis-sentinel
  redis:
    sentinel:
      master: ${redis.sentinel.master}
      nodes: ${redis.sentinel.nodes}
    password: ${redis.password}
    timeout: ${redis.timeout}
    jedis:
      pool:
        max-active: ${redis.jedis.pool.max-active}
        max-idle: ${redis.jedis.pool.max-idle}
        max-wait: ${redis.jedis.pool.max-wait}
        testOnBorrow: ${redis.jedis.pool.testOnBorrow}
        testOnReturn: ${redis.jedis.pool.testOnReturn}
    database: ${redis.database}
    #port: ${redis.port}  #配置哨兵注释单节点信息
    #host: ${redis.host}

application-dev.yml配置

#redis配置
redis:
  sentinel:
    master: mymaster
    nodes: 127.0.0.1:26379  #多个哨兵用,分隔
  password: 123456
  timeout: 60000
  jedis:
    pool:
      max-active: 400 #连接池最大连接数(使用负值表示没有限制)
      max-idle: 20 #连接池中的最大空闲连接
      max-wait: 1000 #连接池最大阻塞等待时间
      testOnBorrow: false
      testOnReturn: false
  database: 0 #Redis数据库索引(默认为0)
  port: 6379
  host: 127.0.0.1

3.连接池配置

@Configuration
@Slf4j
public class JedisConfig {

    //redis哨兵中制定的mastername
    @Value("${spring.redis.sentinel.master:false}")
    private String sentinelMaster;

    //redis哨兵节点
    @Value("${spring.redis.sentinel.nodes: false}")
    private String sentinelNodes;

	/**
     *redis哨兵配置
     */
    @Bean
    @ConditionalOnProperty(prefix="spring.redis.sentinel",  name={"nodes", "master"})
    public RedisSentinelConfiguration redisSentinelConfiguration() {
        RedisSentinelConfiguration configuration = new RedisSentinelConfiguration();
        String[] nodes = sentinelNodes.split(",");
        for(String node:nodes) {
            String[] item = node.split(":");
            String ip = item[0];
            String port = item[1];
            configuration.addSentinel(new RedisNode(ip, Integer.parseInt(port)));
        }
        configuration.setMaster(sentinelMaster);
       log.info("=========== RedisSentinelConfiguration init");
        return configuration;
    }
	/**
     *初始化连接工厂
     */
    @ConfigurationProperties(prefix="spring.redis")
    @Bean
    public JedisConnectionFactory getJedisConnectionFactory(@org.springframework.lang.Nullable RedisSentinelConfiguration sentinelConfig,JedisPoolConfig config) {
        //使用构造方法注入哨兵配置
        JedisConnectionFactory factory = null;
        if (sentinelConfig == null){
            factory = new JedisConnectionFactory(config);
            log.info("=========== JedisConnectionFactory init");
        } else {
            factory = new JedisConnectionFactory(sentinelConfig,config);
            log.info("=========== JedisConnectionFactory from RedisSentinelConfiguration init");
        }
        return factory;
    }
	/**
     * redis连接池配置信息
     */
    @Bean
    @ConfigurationProperties(prefix="spring.redis.jedis.pool")
    public JedisPoolConfig redisPoolConfig() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setTestOnBorrow(true);
        return config;
    }
}

4.RedisTemplate序列化配置

@Configuration
@Slf4j
public class RedisConfig {

    /**
     * redis模板,存储关键字是字符串,值jackson2JsonRedisSerializer是序列化后的值
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory, Jackson2JsonRedisSerializer jackson2JsonRedisSerializer) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        //key
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        //value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();

        log.info("RedisTemplate<String, Object> Bean init success" );
        return redisTemplate;
    }

    /**
     * Jackson2JsonRedisSerializer来序列化和反序列化
     * @return
     */
    @Bean
    public Jackson2JsonRedisSerializer getJackson2JsonRedisSerializer() {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }
}

5.Redis操作工具类

@Component
public class RedisUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 设定key-value值
     * @param key
     * @param object
     * @param time
     */
    public void setExpire(String key, Object object, Long time,TimeUnit timeUnit) {
        // 让该方法能够支持多种数据类型存放
        if (object instanceof String) {
            setString(key, object);
        }
        // 如果存放时Set类型
        if (object instanceof Set) {
            setSet(key, object);
        }
        // 设置有效期
        if (time != null) {
            stringRedisTemplate.expire(key, time, timeUnit);
        }

    }

    /**
     * 设定key-value值
     * @param key
     * @param object
     * @param time
     */
    public void set(String key, Object object, Long time) {
        // 让该方法能够支持多种数据类型存放
        if (object instanceof String) {
            setString(key, object);
        }
        // 如果存放时Set类型
        if (object instanceof Set) {
            setSet(key, object);
        }
        // 设置有效期
        if (time != null) {
            stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
        }

    }

    /**
     * 设置key-value值
     * @param key
     * @param object
     */
    public void setString(String key, Object object) {
        if(object instanceof String){
            String value = (String) object;
            // 存放string类型
            stringRedisTemplate.opsForValue().set(key, value);
        }
    }

    public void setSet(String key, Object object) {
        Set<String> valueSet = (Set<String>) object;
        for (String string : valueSet) {
            stringRedisTemplate.opsForSet().add(key, string);
        }
    }

    /**
     * 获取Key对应值
     * @param key
     * @return
     */
    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 删除key
     * @param key
     */
    public void deleteKey(String key){
        stringRedisTemplate.delete(key);
    }

    /**
     * 获取有序集合数据
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<String> zrange(String key, long start, long end) {
        Set<String> set = stringRedisTemplate.opsForZSet().range(key, start, end);
        return new ArrayList<String>(set);
    }

    /**
     * 获取集合数量
     * @param key
     * @return
     */
    public Long zCard(String key){
        Long value = stringRedisTemplate.opsForZSet().zCard(key);
        return value == null ? 0 : value;
    }

    /**
     * 模糊查询获取keys
     * @param patternKey
     * @return
     */
    public List<String> keys(String patternKey){
        Set<String> set =  stringRedisTemplate.keys(patternKey);
        return new ArrayList<String>(set);
    }

    /**
     * 获取哈希类型对应域的值
     * @param key
     * @param field
     * @return
     */
    public Object hget(String key,Object field){
        return stringRedisTemplate.opsForHash().get(key,field);
    }
}

6.总结

从代码层面比较优雅的解决了Redis哨兵和单节点切换的问题。完成后只需修改配置文件,无需动业务代码即可操作Redis。RedisUtil使用bean注入的方式使用。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://maplefix.top/archives/springboot-redis-sentinel