您的位置:首页 > 数据库 > Redis

RedisCluter集群(二):SpringBoot连接RedisCluter集群

2018-07-08 08:51 351 查看
原文链接:https://my.oschina.net/lyyjason/blog/1842165

1. 添加依赖

以gradle为例:

dependencies {
compile(
"org.springframework.boot:spring-boot-starter-web:$springBootVersion",
"org.springframework.boot:spring-boot-starter-data-redis:${springBootVersion}",
"com.alibaba:fastjson:1.2.44",
)
}

2. 配置application.yml文件

server:
port: 8080

spring:
application:
name: redis-cluster-demo
#Redis Config
redis:
database: 0
timeout: 10000
pool:
maxIdle: 300
minIdle: 50
maxActive: 1000
cluster:
nodes: 172.16.0.15:8001,172.16.0.15:8002,172.16.0.15:8003,172.16.0.15:8004,172.16.0.15:8005,172.16.0.15:8006
connTimeOut: 1000 #连接server超时时间
soTimeOut: 1000 #等待response超时时间
maxAttempts: 2 #连接失败重试次数

3. 配置RedisCluster

package com.jason.redis.cluster.config;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Configuration
public class RedisConfig {

/**
* JedisPool相关配置
*/
@Component
@ConfigurationProperties(prefix = "spring.redis.pool")
public class JedisPoolConfigProp {
Integer maxIdle;
Integer minIdle;
Integer maxActive;

public Integer getMaxIdle() {
return maxIdle;
}

public void setMaxIdle(Integer maxIdle) {
this.maxIdle = maxIdle;
}

public Integer getMinIdle() {
return minIdle;
}

public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}

public Integer getMaxActive() {
return maxActive;
}

public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
}

/**
* Cluster节点相关配置
*/
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigProp {
List<String> nodes;

public List<String> getNodes() {
return nodes;
}

public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}

/**
* Cluster相关配置
*/
@Configuration
public class RedisClusterConfig {

@Autowired
private ClusterConfigProp clusterConfigProp;

@Autowired
private JedisPoolConfigProp jedisPoolConfigProp;

@Bean
public JedisCluster jedisCluster() {
Set<HostAndPort> nodeSet = new HashSet<>();
for (String node : clusterConfigProp.getNodes()) {
String[] split = node.split(":");
nodeSet.add(new HostAndPort(split[0], Integer.valueOf(split[1])));
}

GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(jedisPoolConfigProp.getMaxActive());
poolConfig.setMaxIdle(jedisPoolConfigProp.getMaxIdle());
poolConfig.setMinIdle(jedisPoolConfigProp.getMinIdle());

JedisCluster jedisCluster = new JedisCluster(nodeSet, poolConfig);
return jedisCluster;
}
}
}

4. 编写测试controller

package com.jason.redis.cluster.controller;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import redis.clients.jedis.JedisCluster;

import java.util.Date;

@RestController
@RequestMapping("test")
public class TestController {

@Autowired
private JedisCluster jedisCluster;

@GetMapping("/add/{key}")
@ResponseBody
public JSONObject addKey(@PathVariable String key) {
jedisCluster.set(key, new Date() + "");
JSONObject json = new JSONObject();
json.put("key", key);
json.put("value", jedisCluster.get(key));
return json;
}

@GetMapping("/del/{key}")
@ResponseBody
public JSONObject delKey(@PathVariable String key) {
jedisCluster.del(key);
JSONObject json = new JSONObject();
json.put(key + "是否存在?", jedisCluster.exists(key));
return json;
}
}

5. 打包部署

使用gradle将工程打成一个可运行jar包,并上传至服务器,gradle文件打包相关代码

jar {
String tmpString = ''
configurations.runtime.each { tmpString = tmpString + " lib\\" + it.name }
manifest {
attributes 'Main-Class': 'com.jason.redis.cluster.Application'
attributes 'Class-Path': tmpString
}
}

//清除上次的编译过的文件
task clearPj(type: Delete) {
delete 'build', 'target'
}

//删除临时文件
task release(type: Delete) {
delete 'build/libs/lib', 'build/tmp', 'build/classes', 'build/resources'
}

task packageJar(type: Copy, dependsOn: [build, release])

6. 启动工程

[root@VM_0_15_centos ~]# java -jar redis-cluster-demo-1.0-SNAPSHOT.jar

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::        (v1.5.9.RELEASE)

2018-07-08 08:58:47.047  INFO 27357 --- [           main] com.jason.redis.cluster.Application      : Starting Application on VM_0_15_centos with PID 27357 (/root/redis-cluster-demo-1.0-SNAPSHOT.jar started by root in /root)
部分省略...
2018-07-08 08:58:55.018  INFO 27357 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-07-08 08:58:55.034  INFO 27357 --- [           main] com.jason.redis.cluster.Application      : Started Application in 9.84 seconds (JVM running for 11.085)

7. 向集群中添加几个key

我们调用add接口,添加几个key到redisCluster中,value为系统当前时间,这里我添加了test_key_01,test_key_02,...一直到test_key_10;

[root@VM_0_15_centos ~]# curl http://127.0.0.1:8080/test/add/test_key_01 {"value":"Sun Jul 08 09:15:43 CST 2018","key":"test_key_01"}
省略...
[root@VM_0_15_centos ~]# curl http://127.0.0.1:8080/test/add/test_key_10 {"value":"Sun Jul 08 09:17:02 CST 2018","key":"test_key_10"}

8. 验证主从同步及数据分片

当前集群的slave->master状态为: 8004->8001,8005->8002, 8006->8003

先连接3个主节点,查看当前节点key的情况

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8001
172.16.0.15:8001> keys *
1) "test_key_06"
2) "test_key_02"

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8002
172.16.0.15:8002> keys *
1) "test_key_10"
2) "test_key_03"
3) "test_key_07"

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8003
172.16.0.15:8003> keys *
1) "test_key_05"
2) "test_key_01"
3) "test_key_04"
4) "test_key_08"
5) "test_key_09"

接下来连接3个从节点,查看key的情况

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8004
172.16.0.15:8004> keys *
1) "test_key_02"
2) "test_key_06

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8005
172.16.0.15:8005> keys *
1) "test_key_10"
2) "test_key_03"
3) "test_key_07"

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8006
172.16.0.15:8006> keys *
1) "test_key_09"
2) "test_key_05"
3) "test_key_08"
4) "test_key_01"
5) "test_key_04"

可以看到,主从同步及数据分片都已经OK了。

我们删除一个可以试试,随便选一个test_key_02吧

[root@VM_0_15_centos ~]# curl http://127.0.0.1:8080/test/del/test_key_02 {"test_key_02是否存在?":false}

再到8001和8004查看key的情况,test_key_02都已经被移除了。

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8001
172.16.0.15:8001> keys *
1) "test_key_06"
[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8004
172.16.0.15:8004> keys *
1) "test_key_06"

主从同步再次验证成功!

9. 验证主从切换

我们先停掉8001这个主节点,从理论上讲,8004节点将会升级为主节点,等到8001节点再次启动之后,8001将会作为8004的slave节点,并从8004同步最新的数据,我们来一起验证一下:

直接用kill -9 杀掉8001节点服务,可以看成是模拟服务器宕机的情况。

[root@VM_0_15_centos ~]# ps -ef|grep 8001
root     28063     1  0 09:10 ?        00:00:03 ./src/redis-server 172.16.0.15:8001 [cluster]
root     31305 31248  0 10:21 pts/5    00:00:00 grep --color=auto 8001
[root@VM_0_15_centos ~]# kill -9 28063

此时,进入8004节点,查看其主从状态:

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8004
172.16.0.15:8004> info replication
# Replication
role:master
connected_slaves:0
master_replid:276339bcb6889ad591ee983974d10a023afdc6d4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:6153
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:6153

可以发现,8004已经升级为master节点,并拥有0个slave节点。我们现在继续add一些key进来:

查看8004的key值情况

172.16.0.15:8004> keys *
1) "test_key_15"
2) "test_key_20"
3) "test_key_11"
4) "test_key_19"
5) "test_key_06"

接下来再次启动8001并查看key值:

[root@VM_0_15_centos src]# ./redis-cli -h 172.16.0.15 -p 8001
172.16.0.15:8001> info replication
# Replication
role:slave
master_host:172.16.0.15
master_port:8004
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:336
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f60e42b3eaeded6411af8a79f2c78c310ff8ca0d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:336
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:336

172.16.0.15:8001> keys *
1) "test_key_19"
2) "test_key_06"
3) "test_key_15"
4) "test_key_20"
5) "test_key_11"

当再次启动后,8001同步了8004的key,并由之前的master节点转为slave节点。

以上可以看出,故障转移生效。

我们最后来关注一个异常情况:

我们知道,test_key_20这个名称的key,会路由到8001->8004这个分片,8004为master,8001为slave,现在我们del掉这个key,并且kill掉8004,然后再次添加这个key,看看会发生什么情况。

预想结果可能会因为8004宕机,8001升级为主节点,test_key_20将会保存到8001节点。

验证一下:

[root@VM_0_15_centos ~]# curl http://127.0.0.1:8080/test/del/test_key_20 {"test_key_20是否存在?":false}

[root@VM_0_15_centos ~]# ps -ef|grep 8004
root     31619     1  0 10:27 ?        00:00:00 ./src/redis-server 172.16.0.15:8004 [cluster]
root     32094 26365  0 10:36 pts/4    00:00:00 grep --color=auto 8004
[root@VM_0_15_centos ~]# kill -9 31619

[root@VM_0_15_centos ~]# curl http://127.0.0.1:8080/test/add/test_key_20 {"timestamp":1531017417511,"status":500,"error":"Internal Server Error","exception":"redis.clients.jedis.exceptions.JedisConnectionException","message":"Could not get a resource from the pool","path":"/test/add/test_key_20"}

[root@VM_0_15_centos ~]# curl http://127.0.0.1:8080/test/add/test_key_20 {"value":"Sun Jul 08 10:37:00 CST 2018","key":"test_key_20"}

我们发现,当kill掉8004这个主节点之后,第一次add的时候,应用程序报错了,这个时候,对于test_key_20的add操作丢失了!!!

当第二次add的时候才能成功。 这个问题是因为JedisPool中部分连接失效导致的,第一次应用程序拿到了一个失效的连接,导致操作失败,当第一次操作失败之后,jedisPool会剔除无效连接,因此第二次才可以拿到有效连接去操作redis。当然,这种情况可以通过调节jedisPool的配置属性来尽量减少,但是有点耗性能,不推荐。

也就是说,我们的应用程序,是应该能够容忍极少数的缓存失败的,不要将缓存当作救命稻草,相对来讲,数据库才是数据最可靠的最终载体。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息