【Redis入门】-集群(手动搭建)

使用哨兵模式可以有效的增加数据库容量,同时可以实现自动化,但是,即使使用哨兵模式,redis集群的每个数据库仍然存储着集群中的所有数据,这样就会存在木桶效应:数据库的总容量受限于存储内存最小的redis节点!

而这里讲的集群,是对数据库进行水平扩容,每个节点会存储不同区域的数据。哨兵和集群式两个独立的功能,但从性能上来看哨兵属于集群的子集,当不需要数据分片或者已经在客户端进行分片的场景下哨兵就足够了,但是如果需要水平扩容,则就需要创立集群。(此段摘抄自《Redis入门指南》)。

redis集群(区别于广义的集群)是redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制故障转移等功能。

首先,集群是由多个节点组成的,在没建立集群的时候,每个节点都是独立的,他们处于一个只有自己的集群中,只有让各个节点相互连接起来,才能构成一个包含多个节点的集群。下面开始搭建一个集群。

步骤一:

我以6382、6383、6384三个节点作为例子,让这三个节点组成一个集群。首先,我们需要修改redis配置文件,打开节点集群功能,在redis配置文件中,有一个cluster-enabled开关,它默认是no,我们设置成yes,这样,开启节点之后他本身就处于他自己的集群中,查看一个节点是否已经开启集群使用命令 info cluster ,如果集群被正常开启了,会显示下图所示的信息:

步骤二:

现在,开启了三个节点之后,实际上就存在了三个完全独立的集群,第二步就是把这三个节点链接起来。这个工作使用集群命令 cluster  meet  <ip>  <port> ,这个命令可以让当前节点与目标节点进行“握手”,“握手”成功之后,目标节点就加入到了本节点所在的集群当中。如下图所示:

握手成功会返回 OK 的信息。这样这三个节点已经处于 6382节点的集群当中,我们使用集群命令 cluster  info 查看集群信息:

有两个非常重要的地方需要说明:

1. 集群的数据结构

首先,集群搭建完成之后,每个节点都会有两种不同的结构的数据:

1.1. clusterNode 结构,clusterNode结构保存的是节点本身以及处于集群中的所有节点的状态,节点会为自己创建一个clusterNode结构数据,同时,也会为集群中的所有节点分别创建一个clusterNode结构。clusterNode结构如下图所示:

这里已经对部分重要的信息写了说明,其中slots 与 numslots属性是后文为节点分配槽的时候需要使用的,后面会进行讲解。需要注意的是link属性,这个link属性是一个clusterLink结构,她记录了链接节点所需的有关信息,比如套接字描述符、输入缓冲区、输出缓冲区等。

1.2. 每个节点还有一个结构是clusterState,它存储的是当前节点的视角下,集群目前所处于的状态,比如多少个节点、在线或者下线等,其结构如下图:

其中 *slots是后文为节点分配槽的时候需要使用的,后面会进行讲解。

2. CLUSTER MEET 命令

这个命令实现了节点之间建立集群的动作,它可以让客户端节点将另一个节点添加到自己的集群中。其内部的实现步骤是:

假设节点A接受命令,添加节点B

2.1. 首先,节点A会为节点B创建一个clusterNode结构体,并将这个结构添加到clusterState结构中nodes属性的字典里面,这一步相当是在A中对节点B进行注册。

2.2. 然后,开始握手:

        节点A会向节点B发送MEET消息,此时节点B会为A创建一个clusterNode结构,并将其注册到自己的clusterState中nodes属性中。

        节点B完成对A的注册之后,会反馈给A一个PONG。

如果此时节点A接收到了B的反馈(PONG),那么节点A会再向B发送一个PING。

如果B接受到了A的PING,那么握手完成。

2.3. 握手一旦完成,就说明A已经将B视为自己集群中的节点了,但是还需要得到集群中其他节点的“认可”,因此,节点A会将B的信息通过“Gossip”协议传播给集群中的其他节点,它其他节点也与节点B进行握手,最终,节点B成功加入到A所在的集群中。

步骤三:

至此,这个简单的集群已经搭建完成,那么我们尝试着输入一些命令,系统会返回给你一个错误提示:(error) CLUSTERDOWN The cluster is down,怎么回事呢,这是因为即使我们搭建好了集群,但是这个集群实际上还是处于停止状态,我们可以是用cluster info 命令查看一下集群的状态,我们可以看到第一项就是 cluster_state:fail ,这表明我们的集群还是处于关闭的状态,那么怎样才能开启这个集群让它工作呢?这就是接下来我们的工作:为集群中的节点分配槽!

Redis集群是通过分片的方式保存数据库中的键值对的,集群的整个数据库被分为16384个槽,我们的数据全部分配在这写槽当中,而 接下来,我们就是为不同的节点分配不同片段的槽。数据库中的每个节点都处于0~16383槽中。那么集群怎么算是开启了呢,那就是这16384个槽都有节点在处理的时候,集群就会处于在线状态,反之,只要有一个槽未被处理,那么集群就不会工作!

那么我们如何为节点分配槽呢?我们使用集群命令  cluster addslots <slot> [slot ...],这个命令可以将一个或多个槽分配给指定的节点,准确的说,它是吧一组hash slots分配给接受到命令的节点,如果命令执行成功,节点将指定的 hash slots 映射到自身,节点将获得指定的hash slots,同时开始向集群广播新的配置。例如:

这个命令需要注意:

1. 这个命令只能作用那些还未分配的槽,如果某个槽已经被分配了,那么执行这个命令就会报错;

2. 执行这个命令有一个副作用,如果slot作为其中一个参数设置为importing,一旦节点向自己分配该slot(以前未绑定)这个状态将会被清除;

 

3. 注意一旦一个节点为自己分配了一个slot集合,它就会开始将这个信息在心跳包的头里传播出去。然而其他节点只有在他们有slot没有被其他节点绑定或者传播的新的hash slot的配置年代大于列表中的节点时才会接受这个信息。这意味着这个命令应该仅通过redis集群应用管理客户端例如redsi-trib谨慎使用,而且这个命令如果使用了错误的上下文会导致集群处于错误的状态或者导致数据丢失。

接下来我们就可以为集群中的节点分配槽了,这里,我尝试使用书上介绍的方法一次分配多个槽,使用“...”,但是提示我失败,如下图所示:

于是我尝试使用for循环一个一个分配,还是失败了,于是我用java链接虚拟机,用java写for循环

 

private static final String host = "192.168.45.128";
	
	private static final int post = 6383;
	
	private static ArticalService articalService;
	
	public static void main(String[] args) {
		
		Jedis jedis = JedisTools.getJedis(host, post);
		articalService = new ArticalService();
		System.out.println(jedis.ping());
		for(int i=10919;i<10920;i++) {
			System.out.println(jedis.clusterAddSlots(i));
		}
		System.out.println("结束");
		JedisTools.releaseJedis(jedis);
	}


最终终于将16384个槽分配给了三个节点。节点分配完成之后,使用cluster info命令查看集群状态:

 

如图所示,cluster_state已经变成OK,这表明你的集群已经处于工作状态!我们可以使用  cluster slots  命令查看每个节点的槽指派信息。

接下来,我们来看一下集群中节点是如果保存槽的信息的:

记录节点的槽指派信息(clusterNode.slots[16384]和clusterNode.numslots):
我们看上文的clusterNodes结构中的数据,其中有两个属性我们还没有讲,那就是slots数组和numslots。

其中slots数组记录着在此节点视角下每个槽的状态,如果是自己负责的槽,那么值是1,如果不是自己负责,那值就是0;

而numslots则记录了自己一共负责了多少个槽,也就是slots数组中值是1的个数。

记录集群的槽指派信息(clusterState.*slots[16384]):

上文的clusterState结构中也有一个属性我们没有介绍,那就是 *slots[16384]指针数组,这个数组也记录了集群中的16384个槽,不过不同于clusterNode结构中的slots[16384]数组,clusterState中的slots数组是指针类型的,每个小格指向了当前槽所被负责的节点的clusterNode结构体。

举个例子,在*slots[16384]中,slots[5000]这个小格子代表了第5000槽,假设节点6384负责这个槽,那么slots[5000]就指向了本节点中记录6384节点信息的clusterNode结构;如果是NULL,则表明槽5000目前还没有被分配。

那么既然clusterNode结构和clusterState结构都记录了槽的分配状态,是否是一种多余的机制呢?事实证明,如果仅仅使用clusterNode结构或者clusterState结构来记录槽的指派信息,将无法高效的完成一些问题:

1. 如果仅仅使用clusterNode.slots记录槽的指派信息,我们要想知道 第n 个槽是否已经被指派或者指派给了哪一个节点的时候,我们必须遍历clusterState.nodes数组(该节点记录的其他节点的信息),检查所有的clusterNode结构中的slots数组,直到找到了第n槽被谁负责,或者未被指派,这个过程的时间复杂度是O(N),N是集群中的节点数量。

而如果使用clusterState.*slots[],则时间复杂度是O(1)。

2. 仅仅使用clusterState.*slots[]来记录槽指派信息,这里需要说明一点,每次为节点添加槽的时候,节点都会将自己负责的槽(也就是clusterNode.slots数组)发送给其他节点以便更新状态。若仅仅使用clusterState,那么每次需要发送本节点的槽指派信息的时候,我们都需要整个clusterState.*slots[]数组,记录哪些槽是本节点负责的,然后在发送出去。这样比起clusterNode.slots结构,低效又麻烦。

接下来,我们来看看 cluster  addslots   命令内部的实现过程:

执行:A节点    cluster  addslots 1 2

1. 一旦执行了这个命令,该节点的clusterState.*slots[]数组的所以1和索引2就会指向本节点的clusterNode结构;同时,sluster.Node.slots[]中的索引1和索引2的位置变成了1。

2. 接下来,广播槽指派信息,也就是将自己最新的槽指派信息发送给其他节点。发送的就是本节点的clusterNode.slots[]数组。其他节点(B节点)在接收到这个消息之后,就会更新B节点本身用来记录A节点信息的clusterNode结构。

至此,我们已经讲完了关于集群创建的所有步骤,接下来我们添加一些数据进去吧!

我现在在6382节点输入一个数据:

好奇怪,为什么会出错呢,看提示信息:系统提示了转向另一个redis节点。原来,不同的键会被分配给不同的槽中,如果我们在发送与数据键有关的命令时,接收命令的节点会首先计算出要处理的键属于哪一个槽,如果自己负责这个槽,那么命令将会被处理,如果不是本节点处理的槽,就会返回MOVE错误,并将正确的节点信息返回回去。

我们就拿上图的例子说明:

1. 首先,6382节点接收命令:set k1 v1,那么6382节点先计算这个K1属于哪一个槽,我们可以使用集群命令:cluster keyslot  <key> 来查看键属于哪一个槽。

2. 这里name的槽是5796,然后,6382节点会查询自己的clusterState.*[16384]数组,也就是slots[5796],如果它指向clusterState.myself,则说明是自己负责的,那就回执行命令;但是这里6382节点值负责到5460节点,所以返回MOVE错误,并将slots[5796]指向的clusterNode节点的IP和端口号反馈给客户端。

我们可以在6383节点尝试再次执行:

可以看到,这次成功了。

注意:集群模式的redis-cli 客户端在接收MOVE错误的时候,会自动转向到正确的节点!如果向使用集群模式的redis-cli,在链接redis服务的时候使用 -c ,例如:

redis-cli -c -p 6382

那么就进入集群模式,该模式下MOVED错误会自动转向:

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值