From 38d7c1e8260ce2c7e778a02822df9222be287878 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 18 Oct 2019 08:33:09 +0300 Subject: [PATCH] Redis cluster configuration support --- .../src/main/resources/thingsboard.yml | 35 +++++++- .../dao/cache/TBRedisCacheConfiguration.java | 65 ++++++++++---- .../cache/TBRedisClusterConfiguration.java | 77 ++++++++++++++++ .../cache/TBRedisStandaloneConfiguration.java | 88 +++++++++++++++++++ 4 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 48e37dd646..5c55dc4249 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -278,10 +278,41 @@ redis: # standalone or cluster connection: type: standalone + standalone: host: "${REDIS_HOST:localhost}" port: "${REDIS_PORT:6379}" - db: "${REDIS_DB:0}" - password: "${REDIS_PASSWORD:}" + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" + # this value may be used only if you used not default ClientConfig + clientName: "${REDIS_CLIENT_NAME:standalone}" + # this value may be used only if you used not default ClientConfig + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" + # this value may be used only if you used not default ClientConfig + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" + # this value may be used only if you used not default ClientConfig + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" + cluster: + # Comma-separated list of "host:port" pairs to bootstrap from. + nodes: "${REDIS_NODES:}" + # Maximum number of redirects to follow when executing commands across the cluster. + max-redirects: "${REDIS_MAX_REDIRECTS:12}" + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" + # db index + db: "${REDIS_DB:0}" + # db password + password: "${REDIS_PASSWORD:}" + # pool config + pool_config: + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" # Check new version updates parameters updates: diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java index f1c4b5a8fc..4d56b4cb01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java @@ -31,35 +31,54 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.Assert; import org.thingsboard.server.common.data.id.EntityId; +import redis.clients.jedis.JedisPoolConfig; @Configuration @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false) @EnableCaching @Data -public class TBRedisCacheConfiguration { +public abstract class TBRedisCacheConfiguration { - @Value("${redis.connection.host}") - private String host; + @Value("${redis.pool_config.maxTotal}") + private int maxTotal; - @Value("${redis.connection.port}") - private Integer port; + @Value("${redis.pool_config.maxIdle}") + private int maxIdle; - @Value("${redis.connection.db}") - private Integer db; + @Value("${redis.pool_config.minIdle}") + private int minIdle; - @Value("${redis.connection.password}") - private String password; + @Value("${redis.pool_config.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${redis.pool_config.testOnReturn}") + private boolean testOnReturn; + + @Value("${redis.pool_config.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${redis.pool_config.minEvictableMs}") + private long minEvictableMs; + + @Value("${redis.pool_config.evictionRunsMs}") + private long evictionRunsMs; + + @Value("${redis.pool_config.maxWaitMills}") + private long maxWaitMills; + + @Value("${redis.pool_config.numberTestsPerEvictionRun}") + private int numberTestsPerEvictionRun; + + @Value("${redis.pool_config.blockWhenExhausted}") + private boolean blockWhenExhausted; @Bean public RedisConnectionFactory redisConnectionFactory() { - JedisConnectionFactory factory = new JedisConnectionFactory(); - factory.setHostName(host); - factory.setPort(port); - factory.setDatabase(db); - factory.setPassword(password); - return factory; + return loadFactory(); } + protected abstract JedisConnectionFactory loadFactory(); + @Bean public CacheManager cacheManager(RedisConnectionFactory cf) { DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService(); @@ -78,4 +97,20 @@ public class TBRedisCacheConfiguration { Assert.notNull(registry, "ConverterRegistry must not be null!"); registry.addConverter(EntityId.class, String.class, EntityId::toString); } + + protected JedisPoolConfig buildPoolConfig() { + final JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(maxTotal); + poolConfig.setMaxIdle(maxIdle); + poolConfig.setMinIdle(minIdle); + poolConfig.setTestOnBorrow(testOnBorrow); + poolConfig.setTestOnReturn(testOnReturn); + poolConfig.setTestWhileIdle(testWhileIdle); + poolConfig.setMinEvictableIdleTimeMillis(minEvictableMs); + poolConfig.setTimeBetweenEvictionRunsMillis(evictionRunsMs); + poolConfig.setMaxWaitMillis(maxWaitMills); + poolConfig.setNumTestsPerEvictionRun(numberTestsPerEvictionRun); + poolConfig.setBlockWhenExhausted(blockWhenExhausted); + return poolConfig; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java new file mode 100644 index 0000000000..8405db3c7d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.cache; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Configuration +@ConditionalOnMissingBean(CaffeineCacheConfiguration.class) +@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "cluster") +public class TBRedisClusterConfiguration extends TBRedisCacheConfiguration { + + private static final String COMMA = ","; + private static final String COLON = ":"; + + @Value("${redis.cluster.nodes}") + private String clusterNodes; + + @Value("${redis.cluster.max-redirects}") + private Integer maxRedirects; + + @Value("${redis.cluster.useDefaultPoolConfig}") + private boolean useDefaultPoolConfig; + + @Value("${redis.password}") + private String password; + + public JedisConnectionFactory loadFactory() { + RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); + clusterConfiguration.setClusterNodes(getNodes(clusterNodes)); + clusterConfiguration.setMaxRedirects(maxRedirects); + clusterConfiguration.setPassword(password); + if (useDefaultPoolConfig) { + return new JedisConnectionFactory(clusterConfiguration); + } else { + return new JedisConnectionFactory(clusterConfiguration, buildPoolConfig()); + } + } + + private List getNodes(String nodes) { + List result; + if (StringUtils.isBlank(nodes)) { + result = Collections.emptyList(); + } else { + result = new ArrayList<>(); + for (String hostPort : nodes.split(COMMA)) { + String host = hostPort.split(COLON)[0]; + Integer port = Integer.valueOf(hostPort.split(COLON)[1]); + result.add(new RedisNode(host, port)); + } + } + return result; + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java new file mode 100644 index 0000000000..b09b0dd565 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java @@ -0,0 +1,88 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.cache; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; + +import java.time.Duration; + +@Configuration +@ConditionalOnMissingBean(CaffeineCacheConfiguration.class) +@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "standalone") +public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration { + + @Value("${redis.standalone.host}") + private String host; + + @Value("${redis.standalone.port}") + private Integer port; + + @Value("${redis.standalone.clientName}") + private String clientName; + + @Value("${redis.standalone.connectTimeout}") + private Long connectTimeout; + + @Value("${redis.standalone.readTimeout}") + private Long readTimeout; + + @Value("${redis.standalone.useDefaultClientConfig}") + private boolean useDefaultClientConfig; + + @Value("${redis.standalone.usePoolConfig}") + private boolean usePoolConfig; + + @Value("${redis.db}") + private Integer db; + + @Value("${redis.password}") + private String password; + + public JedisConnectionFactory loadFactory() { + RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(); + standaloneConfiguration.setHostName(host); + standaloneConfiguration.setPort(port); + standaloneConfiguration.setDatabase(db); + standaloneConfiguration.setPassword(password); + if (useDefaultClientConfig) { + return new JedisConnectionFactory(standaloneConfiguration); + } else { + return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig()); + } + } + + private JedisClientConfiguration buildClientConfig() { + if (usePoolConfig) { + return JedisClientConfiguration.builder() + .clientName(clientName) + .connectTimeout(Duration.ofMillis(connectTimeout)) + .readTimeout(Duration.ofMillis(readTimeout)) + .usePooling().poolConfig(buildPoolConfig()) + .build(); + } else { + return JedisClientConfiguration.builder() + .clientName(clientName) + .connectTimeout(Duration.ofMillis(connectTimeout)) + .readTimeout(Duration.ofMillis(readTimeout)).build(); + } + } +} \ No newline at end of file