项目场景:

内网开发时遇到的一个坑点。redis是集群环境,我在使用redis进行单机操作时,会报错。在更换为jediscluster时能进行简单的get,set操作。但是分布式锁等不能操作,更换redis版本后,报错信息在Could not get a resource from the pool 和上述报错信息来回辗转。


版本问题:

redis的版本最好在maven库中去查看对应的版本
http://mvnrepository.com/


原因分析:

下面展示配置文件`。

spring:
  redis:
    password: xxx
    timeout: 5000ms
    lettuce:
      pool:
        max-active: 300
    cluster:
      max-redirects: 3
      nodes:
			- xxx 
			- xxx
      enable: true
    jedis:
      pool:
        max-active: 500
        max-idle: 300
        min-idle: 50
        max-wait: -1ms

从中可以看出,redis连接采用了lettuce和redis两种方式,而这两种方式:
1.Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
2、Lettuce的连接是基于Netty的,连接实例可以在多个线程间共享,如果你不知道Netty也没事,大致意思就是一个多线程的应用可以使用同一个连接实例,而不用担心并发线程的数量。通过异步的方式可以让我们更好地利用系统资源。

代码操作

jedis操作redis集群

static {
        Set<HostAndPort> set = new HashSet<>();
        //外网
       //set.add(new HostAndPort("192.168.9.144",7001));   //set.add的个数看你集群的规模,无限添加
       set.add(new HostAndPort("xxx));
       set.add(new HostAndPort("xxx);
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(100);    //连接池的最大连接数
        poolConfig.setMaxIdle(30);   //连接池的最大链接空闲数
        //jedisCluster = new JedisCluster(set,1000,5000,10,"redis@hisw123",poolConfig);//得到的是redis的集群模式
        jedisCluster = new JedisCluster(set,1000,5000,10,"xxx",poolConfig);//得到的是redis的集群模式
        System.out.println("初始化成功"+jedisCluster);
    }

从中可以看出,是通过jediscluster对redis集群进行操作的,而spring 2.x默认使用lettuce实现

解决方案:

使用lettuce pom依赖需要搭配使用

 <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>

单机版lettuce操作redis

@Configuration
public class RedisConfig {
  @Bean
  public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
    RedisTemplate<String,Object> template = new RedisTemplate <>();
    template.setConnectionFactory(factory);

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    // key采用String的序列化方式
    template.setKeySerializer(stringRedisSerializer);
    // hash的key也采用String的序列化方式
    template.setHashKeySerializer(stringRedisSerializer);
    // value序列化方式采用jackson
    template.setValueSerializer(jackson2JsonRedisSerializer);
    // hash的value序列化方式采用jackson
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();

    return template;
  }
}

使用 jedis客户端 pom需要去除lettuce 并引入redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

使用jedis连接redis

@EnableCaching//开启缓存
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 设置缓存管理器,这里可以配置默认过期时间等
     *
     * @param connectionFactory 连接池
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(60));
        //注意:请勿使用先new 配置对象,然后在调用entryTtl方法的方式来操作
        //会导致配置不生效,原因是调用.entryTtl方法会返回一个新的配置对象,而不是在原来的配置对象上修改

        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

 

        RedisCacheManager manager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
        return manager;
    }

    @SuppressWarnings("all")
    @Bean
    public RedisTemplate<String, String> redisTemplate(JedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    //使用jedis连接池建立jedis连接工厂
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        logger.info("jedisConnectionFactory:初始化了");
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setMaxWaitMillis(maxWaitMillis);
        config.setMaxTotal(maxActive);
        //链接耗尽时是否阻塞,默认true
        config.setBlockWhenExhausted(true);
        //是否启用pool的jmx管理功能,默认true
        config.setJmxEnabled(true);
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setPoolConfig(config);
        factory.setHostName(host);
        factory.setPort(port);
        factory.setPassword(password);
        factory.setDatabase(database);
        factory.setTimeout(timeout);
        return factory;
    }
}

如上,我们可以使用集群版的方式对redis进行操作。
lettcue版本

@Configuration
public class RedisConfig {
 
    @Autowired
    private Environment environment;
    /**
     * 配置lettuce连接池
     *
     * @return
     */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.redis.cluster.lettuce.pool")
    public GenericObjectPoolConfig redisPool() {
        return new GenericObjectPoolConfig();
    }
 
    /**
     * 配置第一个数据源的
     *
     * @return
     */
    @Bean("redisClusterConfig")
    @Primary
    public RedisClusterConfiguration redisClusterConfig() {
 
        Map<String, Object> source = new HashMap<>(8);
        source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));
        RedisClusterConfiguration redisClusterConfiguration;
        redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
        redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));
        return redisClusterConfiguration;
 
    }
 
 
    /**
     * 配置第一个数据源的连接工厂
     * 这里注意:需要添加@Primary 指定bean的名称,目的是为了创建两个不同名称的LettuceConnectionFactory
     *
     * @param redisPool
     * @param redisClusterConfig
     * @return
     */
    @Bean("lettuceConnectionFactory")
    @Primary
    public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("redisClusterConfig") RedisClusterConfiguration redisClusterConfig) {
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
        return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration);
    }
 
 
    /**
     * 配置第一个数据源的RedisTemplate
     * 注意:这里指定使用名称=factory 的 RedisConnectionFactory
     * 并且标识第一个数据源是默认数据源 @Primary
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean("redisTemplate")
    @Primary
    public RedisTemplate redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        return getRedisTemplate(redisConnectionFactory);
 
    }
 
 
 
 
    /**
     * 配置第二个数据源
     *
     * @return
     */
    @Bean("secondaryRedisClusterConfig")
    public RedisClusterConfiguration secondaryRedisConfig() {
 
        Map<String, Object> source = new HashMap<>(8);
        source.put("spring.redis.cluster.nodes", environment.getProperty("spring.secondaryRedis.cluster.nodes"));
        RedisClusterConfiguration redisClusterConfiguration;
        redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
        redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));
 
        return redisClusterConfiguration;
    }
 
    @Bean("secondaryLettuceConnectionFactory")
    public LettuceConnectionFactory secondaryLettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("secondaryRedisClusterConfig")RedisClusterConfiguration secondaryRedisClusterConfig) {
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
        return new LettuceConnectionFactory(secondaryRedisClusterConfig, clientConfiguration);
    }
 
    /**
     * 配置第一个数据源的RedisTemplate
     * 注意:这里指定使用名称=factory2 的 RedisConnectionFactory
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean("secondaryRedisTemplate")
    public RedisTemplate secondaryRedisTemplate(@Qualifier("secondaryLettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        return getRedisTemplate(redisConnectionFactory);
    }
 
 
 
    private RedisTemplate getRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
 
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
 
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
 
        return template;
    }
 
}

jedis版本

/创建一连接,JedisCluster对象,在系统中是单例存在

     Set<HostAndPort> nodes = new HashSet<>();

     nodes.add(new HostAndPort("127.0.0.1", 7001));

     nodes.add(new HostAndPort("127.0.0.1", 7002));

     nodes.add(new HostAndPort("127.0.0.1", 7003));

     nodes.add(new HostAndPort("127.0.0.1", 7004));

     nodes.add(new HostAndPort("127.0.0.1", 7005));

     nodes.add(new HostAndPort("127.0.0.1", 7006));

     JedisCluster cluster = new JedisCluster(nodes);

     //执行JedisCluster对象中的方法,方法和redis一一对应。

     cluster.set("cluster-test", "my jedis cluster test");

     String result = cluster.get("cluster-test");

     System.out.println(result);

     //程序结束时需要关闭JedisCluster对象

     cluster.close();

总结

目前spring2.0以后默认使用的是lettcue,基于netty是新多线程并发安全,如果在你的代码中使用的是jedis去操作redis,那么需要在springdataredis中去除lettuce依赖

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐