SpringBoot系列教程之Redis叢集環境配置
之前介紹的幾篇redis的博文都是基於單機的redis基礎上進行演示說明的,然而在實際的生產環境中,使用redis叢集的可能性應該是大於單機版的redis的,那麼叢集的redis如何操作呢?它的配置和單機的有什麼區別,又有什麼需要注意的呢?
本篇將主要介紹SpringBoot專案整合redis叢集,並針對這個過程中出現的問題進行說明,並給出相應的解決方案
<!-- more -->
I. 環境相關
首先需要安裝redis叢集環境,可以參考博文:redis-叢集搭建手冊
然後初始化springboot專案,對應的pom結構如下
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7</version> <relativePath/> <!-- lookup parent from update --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <java.version>1.8</java.version> </properties> <dependencies> <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> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories
需要注意的是,我們引入了兩個包,一個是必要的 spring-boot-starter-data-redis
,官方封裝的一個操作redis的start工具,藉助它我們可以很方便的直接使用RedisTemplate來操作redis
另外一個是commonos-pool2
這個包,主要是當我們配置了redis的連線池的時候,需要用到它,否則會拋一個Class Not Found 的異常
II. 環境整合及採坑
這裡我將redis叢集搭建在區域網內的一臺centos機器上,從後面的配置檔案也可以看出(為什麼這麼處理?主要是為了引出後面一個問題)
1. application.yml
首我們先按照預設的配置方式,來獲取我們的 RedisTemplate
spring:
redis:
password:
cluster:
nodes: 192.168.0.203:7000,192.168.0.203:7001,192.168.0.203:7002
max-redirects: 3
lettuce:
pool:
max-idle: 16
max-active: 32
min-idle: 8
我們搭建的redis叢集,沒有做主備(否則需要6個例項),為了省事,也沒有設定密碼(生產環境下,這是嚴格禁止的)
2. 使用測試
因為我們採用預設的配置,因此可以直接獲取RedisTemplate的bean物件,來操作redis叢集
@SpringBootApplication
public class Application {
public Application(RedisTemplate redisTemplate) {
redisTemplate.opsForValue().set("spring-r-cluster-1", 123);
redisTemplate.opsForValue().set("spring-r-cluster-2", 456);
redisTemplate.opsForValue().set("spring-r-cluster-3", 789);
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
a. 拒絕連線
上面執行之後,報的第一個錯誤是連線拒絕,而我在redis叢集所在的機器(203)上是可以連線成功的,但是本機連線報錯
出現上面的問題,一般有兩個原因,一個是防火牆導致埠不能外部訪問,一個是redis的配置
防火牆的確認方式
- 判斷防火牆是否開啟:
firewall-cmd --state
如果提示not running 表示未開啟 - 檢視防火牆規則:
firewall-cmd --list-all
然後可以根據實際場景,新增埠
# 永久開啟7000埠的公共訪問許可權
sudo firewall-cmd --zone=public --add-port=7000/tcp --permanent
sudo firewall-cmd --reload
當然在內網的測試環境下,可以直接關閉防火牆
//Disable firewall
systemctl disable firewalld
systemctl stop firewalld
systemctl status firewalld
//Enable firewall
systemctl enable firewalld
systemctl start firewalld
systemctl status firewalld
redis配置
如果確認不是防火牆問題,那麼多半是redis的配置需要修改一下了,在redis.conf
中,有一行bind 127.0.0.1
配置預設開啟,表示只允許本機訪問,其他機器無權訪問
解決辦法就是修改一下這個配置,並重啟
bind 0.0.0.0
b. Unable to connect to 127.0.0.1:7001
執行前面的測試用例時,發現會拋一個奇怪的異常如下
關鍵堆疊資訊如下
Caused by: org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:7001
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:74) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:257) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:718) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.set(LettuceStringCommands.java:143) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.connection.DefaultedRedisConnection.set(DefaultedRedisConnection.java:231) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.core.DefaultValueOperations$3.inRedis(DefaultValueOperations.java:202) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:59) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:198) ~[spring-data-redis-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at com.git.hui.boot.redis.cluster.Application.<init>(Application.java:14) [classes/:na]
at com.git.hui.boot.redis.cluster.Application$$EnhancerBySpringCGLIB$$ac0c03ba.<init>(<generated>) ~[classes/:na]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_171]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_171]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_171]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_171]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:170) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
... 19 common frames omitted
Caused by: io.lettuce.core.RedisException: io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:7001
at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:125) ~[lettuce-core-5.0.4.RELEASE.jar:na]
at io.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:118) ~[lettuce-core-5.0.4.RELEASE.jar:na]
at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80) ~[lettuce-core-5.0.4.RELEASE.jar:na]
at com.sun.proxy.$Proxy44.set(Unknown Source) ~[na:na]
at org.springframework.data.red
通過斷點可以看到,叢集中的節點ip/埠是準確的,但是異常提示出來個無法連線127.0.0.1:7001
,出現這個問題的原因,主要是我們在建立redis叢集的時候,設定叢集節點使用如下面的命令
redis/src/redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002
通過上面這種方式建立的redis叢集,並沒有什麼問題,但是在springbot的整合中,通過redis叢集獲取到的節點資訊就是127.0.0.1:7000
... 然後導致上面的問題,因此一個解決辦法是在建立叢集的時候,指定下ip
首先資料和配置,然後重新建立叢集關係
# 刪除資料配置
rm xxx/data/*
redis/src/redis-cli --cluster create 192.168.0.203:7000 192.168.0.203:7001 192.168.0.203:7002
然後再次測試ok
3. jedis配置
前面的配置預設會使用letttuce作為redis的橋接工具,如果我們底層想使用jedis,可以怎麼操作?
首先在pom依賴中新增jedis依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
yml檔案中的配置基本上不改都ok,在實際的專案中,對連線池稍微改了一下,不影響閱讀,這裡不貼出
接下來是定義RedisConnectionFactoy
來替換預設的
下面的配置和以前的一篇博文 181101-SpringBoot高階篇Redis之Jedis配置 基本差不多,需要注意的是我們使用
RedisClusterConfiguration
替換了RedisStandaloneConfiguration
@Configuration
public class RedisAutoConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPool,
RedisClusterConfiguration jedisConfig) {
JedisConnectionFactory factory = new JedisConnectionFactory(jedisConfig, jedisPool);
factory.afterPropertiesSet();
return factory;
}
@Configuration
public static class JedisConf {
@Value("${spring.redis.cluster.nodes:127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002}")
private String nodes;
@Value("${spring.redis.cluster.max-redirects:3}")
private Integer maxRedirects;
@Value("${spring.redis.password:}")
private String password;
@Value("${spring.redis.database:0}")
private Integer database;
@Value("${spring.redis.jedis.pool.max-active:8}")
private Integer maxActive;
@Value("${spring.redis.jedis.pool.max-idle:8}")
private Integer maxIdle;
@Value("${spring.redis.jedis.pool.max-wait:-1}")
private Long maxWait;
@Value("${spring.redis.jedis.pool.min-idle:0}")
private Integer minIdle;
@Bean
public JedisPoolConfig jedisPool() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
return jedisPoolConfig;
}
@Bean
public RedisClusterConfiguration jedisConfig() {
RedisClusterConfiguration config = new RedisClusterConfiguration();
String[] sub = nodes.split(",");
List<RedisNode> nodeList = new ArrayList<>(sub.length);
String[] tmp;
for (String s : sub) {
tmp = s.split(":");
// fixme 先不考慮異常配置的case
nodeList.add(new RedisNode(tmp[0], Integer.valueOf(tmp[1])));
}
config.setClusterNodes(nodeList);
config.setMaxRedirects(maxRedirects);
config.setPassword(RedisPassword.of(password));
return config;
}
}
}
然後其他的依舊,此時RedisTemplate的底層連線就變成了Jedis
III. 其他
0. 專案&相關博文
- 工程:https://github.com/liuyueyi/spring-boot-demo
- moduel : https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/124-redis-cluster
關聯博文
1. 一灰灰Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰Blog個人部落格 https://blog.hhui.top
- 一灰灰Blog-Spring專題部落格 http://spring.hhui.top