RedisCluter集群(二):SpringBoot连接RedisCluter集群
2018-07-08 08:51
351 查看
原文链接:https://my.oschina.net/lyyjason/blog/1842165
1. 添加依赖
以gradle为例:
2. 配置application.yml文件
3. 配置RedisCluster
4. 编写测试controller
5. 打包部署
使用gradle将工程打成一个可运行jar包,并上传至服务器,gradle文件打包相关代码
6. 启动工程
7. 向集群中添加几个key
我们调用add接口,添加几个key到redisCluster中,value为系统当前时间,这里我添加了test_key_01,test_key_02,...一直到test_key_10;
8. 验证主从同步及数据分片
当前集群的slave->master状态为: 8004->8001,8005->8002, 8006->8003
先连接3个主节点,查看当前节点key的情况
接下来连接3个从节点,查看key的情况
可以看到,主从同步及数据分片都已经OK了。
我们删除一个可以试试,随便选一个test_key_02吧
再到8001和8004查看key的情况,test_key_02都已经被移除了。
主从同步再次验证成功!
9. 验证主从切换
我们先停掉8001这个主节点,从理论上讲,8004节点将会升级为主节点,等到8001节点再次启动之后,8001将会作为8004的slave节点,并从8004同步最新的数据,我们来一起验证一下:
直接用kill -9 杀掉8001节点服务,可以看成是模拟服务器宕机的情况。
此时,进入8004节点,查看其主从状态:
可以发现,8004已经升级为master节点,并拥有0个slave节点。我们现在继续add一些key进来:
查看8004的key值情况
接下来再次启动8001并查看key值:
当再次启动后,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节点。
验证一下:
我们发现,当kill掉8004这个主节点之后,第一次add的时候,应用程序报错了,这个时候,对于test_key_20的add操作丢失了!!!
当第二次add的时候才能成功。 这个问题是因为JedisPool中部分连接失效导致的,第一次应用程序拿到了一个失效的连接,导致操作失败,当第一次操作失败之后,jedisPool会剔除无效连接,因此第二次才可以拿到有效连接去操作redis。当然,这种情况可以通过调节jedisPool的配置属性来尽量减少,但是有点耗性能,不推荐。
也就是说,我们的应用程序,是应该能够容忍极少数的缓存失败的,不要将缓存当作救命稻草,相对来讲,数据库才是数据最可靠的最终载体。
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的配置属性来尽量减少,但是有点耗性能,不推荐。
也就是说,我们的应用程序,是应该能够容忍极少数的缓存失败的,不要将缓存当作救命稻草,相对来讲,数据库才是数据最可靠的最终载体。
相关文章推荐
- Springboot2.X集成redis集群(Lettuce)连接的方法
- spring boot下JedisCluster方式连接Redis集群的配置
- Spring Boot 连接 redis 集群
- 基于springboot,quartz,mysql搭建quartz集群
- ssm springboot springcloud redis shiro dubbo 分布式集群 支付结算等架构教程视频
- docker连接spring boot和mysql容器方法介绍
- Spring Boot JDBC 连接数据库
- springboot + redis集群
- Spring Boot + Redis 实现Shiro集群
- Spring Boot 连接MySql数据库
- 实现Spring Boot、 Redis、 Shiro集群
- 集群与负载均衡系列(5)——消息队列之spring-boot整合Rabbitmq
- SpringBoot之Mybatis连接MySQL进行CRUD(注解&配置文件)(简测试版)
- 关于spring boot整合druid连接池在程序关闭后连接不释放问题解决
- Netty(一) SpringBoot 整合长连接心跳机制
- springboot+Junit测试rest接口,报错显示url无法连接
- Spring Boot使用Spring Data Redis操作Redis(单机/集群)
- spring boot整合mybatis使用c3p0数据源连接mysql
- Springboot 项目中 手动取出连接执行sql,结束后将连接交还给连接池
- spring boot 连接数据库