Redis之——Redis集群的高可用测试与集群重建

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/72669962

本博文是在《Redis之——Redis 集群的安装(Redis+CentOS)》   基础上测试的,请先阅读博文《Redis之——Redis 集群的安装(Redis+CentOS)》再进行本博文的学习实践。

一、Redis 集群的使用测试(Jedis 客户端的使用)

1、 Jedis 客户端建议升级到最新版, 这样对 3.x.x 集群有比较好的支持。

https://github.com/xetorthio/jedis
http://mvnrepository.com/artifact/redis.clients/jedis

2、 直接在 Java 代码中链接 Redis 集群   

package com.lyz.demo.redis;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 
 * @描述: Redis集群测试 .
 * @作者: liuyazhuang
 * @创建时间: 2017-05-23
 * @版本号: V1.0 .
 */
public class RedisClusterTest {
	private static final Log log = LogFactory.getLog(RedisClusterTest.class);

	public static void main(String[] args) {
		
		// 数据库链接池配置
		JedisPoolConfig config = new JedisPoolConfig();  
        config.setMaxTotal(100);  
        config.setMaxIdle(50);  
        config.setMinIdle(20);  
        config.setMaxWaitMillis(6 * 1000);  
        config.setTestOnBorrow(true);  
		
		// Redis集群的节点集合
		Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
		jedisClusterNodes.add(new HostAndPort("192.168.1.111", 7111));
		jedisClusterNodes.add(new HostAndPort("192.168.1.112", 7112));
		jedisClusterNodes.add(new HostAndPort("192.168.1.113", 7113));
		jedisClusterNodes.add(new HostAndPort("192.168.1.114", 7114));
		jedisClusterNodes.add(new HostAndPort("192.168.1.115", 7115));
		jedisClusterNodes.add(new HostAndPort("192.168.1.116", 7116));
		
		// 根据节点集创集群链接对象
		//JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes);
		// 集群各节点集合,超时时间,最多重定向次数,链接池
		JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, 2000, 100, config);
		int num = 1000;
		String key = "lyz";
		String value = "";
		for (int i=1; i <= num; i++){
			// 存数据
			jedisCluster.set(key+i, "liuyazhuang"+i); 
			// 取数据
			value = jedisCluster.get(key+i); 
			log.info(key+i + "=" + value);
			// 删除数据
			//jedisCluster.del(key+i); 
			//value = jedisCluster.get(key+i); 
			//log.info(key+i + "=" + value);
		}
	}
}

3、Spring 配置 Jedis 链接 Redis3.0 集群的配置

spring-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Jedis链接池配置,注意:Jedis版本建议升级到最新 -->
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<property name="maxTotal" value="100" />
		<property name="maxIdle" value="20" />
		<property name="minIdle" value="10" />
		<property name="blockWhenExhausted" value="true"></property>
		<property name="maxWaitMillis" value="3000" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="testWhileIdle" value="true" />
		<property name="minEvictableIdleTimeMillis" value="60000" />
		<property name="timeBetweenEvictionRunsMillis" value="30000" />
		<property name="numTestsPerEvictionRun" value="-1" />
	</bean>

	<!-- JedisCluster -->
	<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
		<constructor-arg index="0">
			<set>
				<bean class="redis.clients.jedis.HostAndPort">
					<constructor-arg index="0" value="192.168.1.111" />
					<constructor-arg index="1" value="7111" type="int" />
				</bean>
				<bean class="redis.clients.jedis.HostAndPort">
					<constructor-arg index="0" value="192.168.1.112" />
					<constructor-arg index="1" value="7112" type="int" />
				</bean>
				<bean class="redis.clients.jedis.HostAndPort">
					<constructor-arg index="0" value="192.168.1.113" />
					<constructor-arg index="1" value="7113" type="int" />
				</bean>
				<bean class="redis.clients.jedis.HostAndPort">
					<constructor-arg index="0" value="192.168.1.114" />
					<constructor-arg index="1" value="7114" type="int" />
				</bean>
				<bean class="redis.clients.jedis.HostAndPort">
					<constructor-arg index="0" value="192.168.1.115" />
					<constructor-arg index="1" value="7115" type="int" />
				</bean>
				<bean class="redis.clients.jedis.HostAndPort">
					<constructor-arg index="0" value="192.168.1.116" />
					<constructor-arg index="1" value="7116" type="int" />
				</bean>
			</set>
		</constructor-arg>
		<constructor-arg index="1" value="2000" type="int"></constructor-arg>
		<constructor-arg index="2" value="100" type="int"></constructor-arg>
		<constructor-arg index="3" ref="jedisPoolConfig"></constructor-arg>
	</bean>

</beans>
RedisClusterSpringTest.java
package com.lyz.demo.redis;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import redis.clients.jedis.JedisCluster;

/**
 * 
 * @描述: Redis集群使用测试 .
 * @作者: liuyazhuang
 * @创建时间: 2017-05-23
 * @版本号: V1.0 .
 */
public class RedisClusterSpringTest {
	private static final Log log = LogFactory.getLog(RedisClusterSpringTest.class);

	public static void main(String[] args) {
		try {
			ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-context.xml");
			context.start();
			
			JedisCluster jedisCluster = (JedisCluster) context.getBean("jedisCluster");
			int num = 1000;
			String key = "lyz";
			String value = "";
			for (int i=1; i <= num; i++){
				// 存数据
				//jedisCluster.set(key+i, "liuyazhuang"+i);
				//jedisCluster.setex(key+i, 60, "liuyazhuang"+i); // 设置有效时间
				
				// 取数据
				value = jedisCluster.get(key+i); 
				log.info(key+i + "=" + value);
				
				// 删除数据
				//jedisCluster.del(key+i); 
				//value = jedisCluster.get(key+i); 
				//log.info(key+i + "=" + value);
			}

			context.stop();
		} catch (Exception e) {
			log.error("==>RedisSpringTest context start error:", e);
			System.exit(0);
		} finally {
			log.info("===>System.exit");
			System.exit(0);
		}
	}
}
RedisClusterFailOverTest.java
package com.lyz.demo.redis;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 
 * @描述: Redis集群测试 .
 * @作者: liuyazhuang
 * @创建时间: 2017-05-23
 * @版本号: V1.0 .
 */
public class RedisClusterFailoverTest {
	private static final Log log = LogFactory.getLog(RedisClusterFailoverTest.class);

	public static void main(String[] args) {
		
		// 数据库链接池配置
		JedisPoolConfig config = new JedisPoolConfig();  
        config.setMaxTotal(100);  
        config.setMaxIdle(50);  
        config.setMinIdle(20);  
        config.setMaxWaitMillis(6 * 1000);  
        config.setTestOnBorrow(true);  
		
		// Redis集群的节点集合
		Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
		jedisClusterNodes.add(new HostAndPort("192.168.1.111", 7111));
		jedisClusterNodes.add(new HostAndPort("192.168.1.112", 7112));
		jedisClusterNodes.add(new HostAndPort("192.168.1.113", 7113));
		jedisClusterNodes.add(new HostAndPort("192.168.1.114", 7114));
		jedisClusterNodes.add(new HostAndPort("192.168.1.115", 7115));
		jedisClusterNodes.add(new HostAndPort("192.168.1.116", 7116));
		
		try {
			
			// 根据节点集创集群链接对象
			//JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes);
			// 集群各节点集合,超时时间,最多重定向次数,链接池
			JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, 2000, 100, config);
			int num = 1000;
			String key = "lyz";
			String value = "";
			int count = 1;

			while(true){
				for (int i=1; i <= num; i++){
					try {
						// 存数据
						//jedisCluster.set(key+i, "liuyazhuang"+i); 
						
						// 取数据
						value = jedisCluster.get(key+i); 
						log.info(key+i + "=" + value);
						if (value == null || "".equals(value)){
							log.error("===>break" + key+i + " value is null");
							break;
						}
					} catch (Exception e) {
						log.error("====>", e);
						Thread.sleep(3000);
						continue;
					}
					// 删除数据
					//jedisCluster.del(key+i); 
					//value = jedisCluster.get(key+i); 
					//log.info(key+i + "=" + value);
				}
				log.info("===================================>count:" + count);
				if (value == null || "".equals(value)){
					break;
				}
				count++;
				Thread.sleep(1000);
			}
		} catch (Exception e) {
			log.error("====>", e);
		}
		
	}
}

二、 Redis 集群特点

1、 集群架构特点

(1)所有的 redis 节点彼此互联(PING-PONG 机制), 内部使用二进制协议优化传输速度和带宽;
(2)节点的 fail 是通过集群中超过半数的节点检测失效时才生效;
(3)客户端与 redis 节点直连, 不需要中间 proxy 层。 客户端不需要连接集群所有节点, 连接集群中任何一个可用节点即可;
(4)redis-cluster 把所有的物理节点映射到[0-16383]个 slot(哈希槽)上, cluster 负责维护node<->slot<->value 。

2、 集群选举容错

(1)节点失效选举过程是集群中所有 master 参与,如果半数以上 master 节点与当前被检测 master 节点通信检测超时(cluster-node-timeout), 就认为当前 master 节点挂掉;
(2):什么时候整个集群不可用(cluster_state:fail)
A:如果集群任意 master 挂掉, 且当前 master 没有 slave。 集群进入 fail 状态,也可以理解成集群的slot 映射[0-16383]不完整时进入 fail 状态。 ps : redis-3.0.0.rc1 加入 cluster-require-fullcoverage 参数,默认关闭,打开集群兼容部分失败;
B:如果集群超过半数以上 master 挂掉,无论是否有 slave 集群进入 fail 状态。 ps:当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误。

三、 客户端集群命令

1、集群

cluster info :打印集群的信息
cluster nodes :列出集群当前已知的所有节点(node),以及这些节点的相关信息。

2、节点

cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
cluster forget <node_id> :从集群中移除 node_id 指定的节点。
cluster replicate <node_id> :将当前节点设置为 node_id 指定的节点的从节点。
cluster saveconfig :将节点的配置文件保存到硬盘里面。

3、槽(slot)

cluster addslots <slot> [slot ...] :将一个或多个槽(slot)指派(assign)给当前节点。
cluster delslots <slot> [slot ...] :移除一个或多个槽对当前节点的指派。
cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
cluster setslot <slot> node <node_id> :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给
另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
cluster setslot <slot> migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。
cluster setslot <slot> importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。
cluster setslot <slot> stable :取消对槽 slot 的导入(import)或者迁移(migrate)。

4、键

cluster keyslot <key> :计算键 key 应该被放置在哪个槽上。
cluster countkeysinslot <slot> :返回槽 slot 目前包含的键值对数量。
cluster getkeysinslot <slot> <count> :返回 count 个 slot 槽中的键。

四、集群高可用测试

1、 重建集群, 步骤

(1)关闭集群的各节点;
(2)删除各节点数据目录下的 nodes.conf、 appendonly.aof、 dump.rdb;

# rm -rf appendonly.aof | rm -rf dump.rdb | rm -rf nodes.conf
(3)重新启用所有的节点
192.168.1.111
# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7111/redis-7111.conf
192.168.1.112
# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7112/redis-7112.conf
192.168.1.113
# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7113/redis-7113.conf
192.168.1.114
# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7114/redis-7114.conf
192.168.1.115
# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7115/redis-7115.conf
192.168.1.116
# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7116/redis-7116.conf
(4)执行集群创建命令(只需要在其中一个节点上执行一次则可)
# cd /usr/local/src/redis-3.2.9/src/
# cp redis-trib.rb /usr/local/bin/redis-trib
# redis-trib create --replicas 1 192.168.1.114:7114 192.168.1.115:7115 192.168.1.116:7116 192.168.1.111:7111 192.168.1.112:7112 192.168.1.113:7113

2、查看当前集群各节点的状态

[root@liuyazhuang01 7111]# /usr/local/redis3/bin/redis-cli -c -p 7111 
127.0.0.1:7111> cluster nodes

3、使用 demo 应用向集群写入 1000 个键值数据

使用 /usr/local/redis3/bin/redis-cli -c -p 711X 命令登录各节点,使用 keys * 查看各节点的所有key

4、运行 demo 应用, 获取所有的键值数据

如果有空值则停止

5、 模拟集群节点宕机

(1)Jedis 客户端循环操作集群数据(模拟用户持续使用系统)
(2)查看 Redis 集群当前状态(用于接下来做节点状态变化对比)

(3)关闭其中一个 master 节点(7111)

(4)观察该 master 节点和对应的 slave 节点的状态变化

节点状态 fail? 表示正在判断是否失败
节点状态 fail 表示节点失败,对应的 slave 节点提升为 master

(5)再查看集群状态变化# /usr/local/src/redis-3.2.9/src/redis-trib.rb check 192.168.1.116:7116


由上可见, 7114 节点替换 7111, 由 slave 变成了 master此时再执行 demo 应用获取所有的键值数据,依然正常,说明 slave 替换 master 成功,集群正常。

6、 恢复 fail 节点

(1)启动 7111

# /usr/local/redis3/bin/redis-server /usr/local/redis3/cluster/7111/redis-7111.conf
(2)查看集群状态

其中 7111 变成 7114 的 slave

7、观察集群节点切换过程中, 对客户端的影响

JedisCluster 链接 Redis 集群操作时遇到的几个常见异常
(1)重定向次数过多
redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections?
解决方法: 初始化 JedisCluster 时,设定 JedisCluster 的 maxRedirections
//集群各节点集合,超时时间(默认 2 秒),最多重定向次数(默认 5),链接池
new JedisCluster(jedisClusterNodes, 2000, 100, config);
(2)集群不可以用
redis.clients.jedis.exceptions.JedisClusterException: CLUSTERDOWN The cluster is down
原因: 集群节点状态切换过程中会出现临时闪断,客户端重试操作则可。
(3)链接超时
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
解决方法: 初始化 JedisCluster 时,设定 JedisCluster 的 timeout(默认为两秒);也可以修改源码中的默认时间

8、 总结

优点:
在 master 节点下线后, slave 节点会自动提升为 master 节点, 保存集群持续提供服务;fail 节点恢复后, 会自动添加到集群中,变成 slave 节点;
缺点:
由于 redis 的复制使用异步机制,在自动故障转移的过程中,集群可能会丢失写命令。然而 redis 几乎是同时执行(将命令恢复发送给客户端,以及将命令复制到 slave 节点)这两个操作,所以实际中,命令丢失的窗口非常小。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰 河

可以吃鸡腿么?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值