1. Предыстория проблемы
В проекте загрузки Spring Redis необходимо использовать в качестве кеша, поэтому будет использоваться spring-boot-starter-data-redis Конкретные зависимости следующие:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
В тестовой среде проблем в функциональных тестах и нагрузочных тестах обнаружено не было, причина в том, что в тестовой среде Redis собирался сам и пароль не устанавливался, однако после выхода в онлайн Redis использовал кластер сервиса Azure Pass, и установите пароль Процесс использования Следующие проблемы были обнаружены в:
- высокая нагрузка на Redis;
- Исключение Redis, сообщение об ошибке:
com.lambdaworks.redis.RedisException: java.lang.IllegalArgumentException: Connection to XXX.XX.XXX.XXX:15000 not allowed. This connection point is not known in the cluster viewjava.lang.IllegalArgumentException: Connection to XXX.XX.XXX.XXX:15000 not allowed. This connection point is not known in the cluster viewConnection to XXX.XX.XXX.XXX:15000 not allowed. This connection point is not known in the cluster view
;
2. Анализ проблемы + решение
2.1, нагрузка на Redis слишком высока
2.1.1, причина проблемы
Первоначально я планировал проверить, не была ли проблема с логикой кода причиной слишком высокой нагрузки Redis, поэтому я вошел на сервер Redis и использовал команду монитора, чтобы наблюдать за частотой выполнения команды, и обнаружил, что каждый раз, когда команда был выполнен, он был выполнен один раз.Auth password
команда, указывающая на то, что пул соединений используется неправильно, что приводит к выполнению одной команды для создания одного соединения, что приводит к высокой нагрузке и низкой эффективности выполнения кода.
2.1.2, решение
Затем я сравнил проекты, использующие JedisCluster, без таких проблем, поэтому я заподозрил, что это проблема с RedisTemplate из spring-boot-starter-data-redis.Проверив исходный код, я обнаружил, что пакет драйверов spring-data-redis была заменена определенной версией.Lettuce
# 错误配置
# Redis配置
spring.redis.cluster.nodes=127.0.0.1:6379
### 连接超时时间(毫秒)
spring.redis.timeout=60000
spring.redis.password=xxxxxxx
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
##连接池最大阻塞等待时间,若使用负值表示没有限制
spring.redis.jedis.pool.max-wait=-1
##连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
Его нужно изменить на правильную конфигурацию.После модификации такого явления нет.Конкретная конфигурация выглядит следующим образом:
единое видение:
# 单机版
# Redis配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
### 连接超时时间(毫秒)
spring.redis.timeout=60000
spring.redis.password=xxxxxxx
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
##连接池最大阻塞等待时间,若使用负值表示没有限制
spring.redis.jedis.pool.max-wait=-1
##连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
Версия кластера:
#集群版
# Redis配置
spring.redis.cluster.nodes=127.0.0.1:6379
### 连接超时时间(毫秒)
spring.redis.timeout=60000
spring.redis.password=xxxxxxx
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
##连接池最大阻塞等待时间,若使用负值表示没有限制
spring.redis.lettuce.pool.max-wait=-1
##连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
Примечание. Чтобы включить версию кластера, необходимо добавить следующие дополнительные зависимости.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
2.2 Исключение Redis Подключение к XXX.XX.XXX.XXX:15000 не разрешено.
2.2.1, причина проблемы
Я искал в Интернете и обнаружил, что на github проекта есть отзывы и решения этой проблемы.GitHub.com/lettuce-IO/…, причина в том, что в Салат есть элемент конфигурацииvalidateClusterNodeMembership
Значение по умолчанию — истинная причина;
2.2.2, решение
Поскольку spring boot не может напрямую изменить эту конфигурацию через файл конфигурации, необходимо настроить конфигурацию Redis.Конкретный код выглядит следующим образом: MylettuceConnectionFactory.java
package com.quison.test.config;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import java.util.concurrent.TimeUnit;
public class MyLettuceConnectionFactory extends LettuceConnectionFactory {
public MyLettuceConnectionFactory() { }
public MyLettuceConnectionFactory(RedisClusterConfiguration redisClusterConfiguration, LettuceClientConfiguration lettuceClientConfiguration) {
super(redisClusterConfiguration, lettuceClientConfiguration);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
DirectFieldAccessor accessor = new DirectFieldAccessor(this);
AbstractRedisClient client = (AbstractRedisClient) accessor.getPropertyValue("client");
if(client instanceof RedisClusterClient){
RedisClusterClient clusterClient = (RedisClusterClient) client;
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enablePeriodicRefresh(10, TimeUnit.MINUTES)
.enableAllAdaptiveRefreshTriggers()
.build();
ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
// 注意此配置项设置为false
.validateClusterNodeMembership(false)
.topologyRefreshOptions(topologyRefreshOptions)
.build();
clusterClient.setOptions(clusterClientOptions);
}
}
}
Поскольку пул соединений также необходимо установить самостоятельно после настройки, файл конфигурации Redis изменяется на следующие параметры. RedisConfig.java
package com.quison.test.config;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.DefaultLettucePool;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Configuration
public class RedisConfig {
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.lettuce.pool.max-idle}")
private Integer maxIdle;
@Value("${spring.redis.lettuce.pool.max-active}")
private Integer maxActive;
@Value("${spring.redis.cluster.max-redirects}")
private Integer maxRedirects;
@Bean
public RedisConnectionFactory myRedisConnectionFactory() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
String[] serverArray = clusterNodes.split(",");
Set<RedisNode> nodes = new HashSet<RedisNode>();
for (String ipPort : serverArray) {
String[] ipAndPort = ipPort.split(":");
nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
}
redisClusterConfiguration.setPassword(RedisPassword.of(password));
redisClusterConfiguration.setClusterNodes(nodes);
redisClusterConfiguration.setMaxRedirects(maxRedirects);
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMinIdle(8);
genericObjectPoolConfig.setMaxTotal(maxActive);
genericObjectPoolConfig.setMaxWaitMillis(10000);
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(10000))
.poolConfig(genericObjectPoolConfig)
.build();
return new MyLettuceConnectionFactory(redisClusterConfiguration, clientConfig);
}
/**
* redis模板,存储关键字是字符串,值是Jdk序列化
*
* @param myRedisConnectionFactory
* @return
* @Description:
*/
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@Primary
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory myRedisConnectionFactory) {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(myRedisConnectionFactory);
//key序列化方式;但是如果方法上有Long等非String类型的话,会报类型转换错误;
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
//默认使用JdkSerializationRedisSerializer序列化方式;会出现乱码,改成StringRedisSerializer
StringRedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
return redisTemplate;
}
}
3. Резюме
Ешь кусок, расти мудрость, резюмируя следующим образом:
- Среда разработки + тестирования должна максимально соответствовать онлайну, чтобы проблемы можно было найти заранее;
- Использование новой технологии требует большого количества испытаний, прежде чем запустить ее в производство;