java操作redis集群时无法连接到节点 No reachable node in cluster 或者 too many cluster redirections 等报错
项目场景:内网开发时遇到的一个坑点。redis是集群环境,我在使用redis进行单机操作时,会报错。在更换为jediscluster时能进行简单的get,set操作。但是分布式锁等不能操作,更换redis版本后,报错信息在Could not get a resource from the pool 和上述报错信息来回辗转。版本问题:redis的版本最好在maven库中去查看对应的版本http://
项目场景:
内网开发时遇到的一个坑点。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依赖
更多推荐
所有评论(0)