Redis原理详解系列-Redis集群

Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。

节点

一个Redis集群通常由多个节点组成,刚开始的时候,每个节点都是相互独立运行的,一个节点,就是一个单独运行的Redis服务器,他们都处于一个只包含自己的集群当中,要建立一个真正可工作的集群,我们必须将各个节点连接起来,构成一个包含多个节点的集群。

打开集群:

cluster-enabled选项设置为yes

Redis原理详解系列-Redis集群_第1张图片

假设有三个独立的节点8000,8001,8002(暂时使用端口号区分节点)

一开始redis.conf的cluster-enabled选项为no

1.修改为:cluster-enabled yes

2.

启动8000端口的redis-server:

[root@localhost 8000]# ./bin/redis-server redis.conf

8000文件夹的结构如下,这个文件夹包含了整个redis程序组件

Redis原理详解系列-Redis集群_第2张图片

Redis原理详解系列-Redis集群_第3张图片

bin目录:

Redis原理详解系列-Redis集群_第4张图片

redis.conf内容:
port 8000
cluster-enabled yes

3.

使用客户端连接服务器:

[root@localhost 8000]# ./bin/redis-cli -c -p 8000
127.0.0.1:8000>

4.

输入cluster nodes

127.0.0.1:8000> cluster nodes
0bb7aac0ce20c93923884be95f01bd9856c7cf19 :8000@18000 myself,master - 0 0 0 connected

可以看到此时集群只有8000端口一个节点.

将其他节点加入集群:

  1. 启动8001端口的节点
[root@localhost 8001]# ./bin/redis-server redis.conf
[root@localhost 8001]# ps -x|grep redis
 1284 ?        Ssl    0:00 ./bin/redis-server *:8000 [cluster]
 1292 ?        Ssl    0:00 ./bin/redis-server *:8001 [cluster]
 1297 pts/0    S+     0:00 grep --color=auto redis
[root@localhost 8001]#

此时查询进程会发现有两个redis-server在运行

通过cluster meet 命令可以将指定的节点添加到执行命令的redis客户端所在的集群中

Redis原理详解系列-Redis集群_第5张图片

clusterNode的数据结构

Redis原理详解系列-Redis集群_第6张图片

使用cluster meet命令连接节点

实际操作,cluster meet执行之前节点的状态

  1. 执行cluster meet

Redis原理详解系列-Redis集群_第7张图片

  1. 打开8001节点的客户端查看节点信息

Redis原理详解系列-Redis集群_第8张图片

此时可以看到8001节点显示8001节点自己为myself,而且多出来了8000的节点信息

可以发现节点列表中多出了8001的节点信息,通过刚才的握手流程我们可以得知,不管是连接8000还是8001,8000与8001节点握手的过程会让两个节点都在自己的集群信息中存储其他节点的连接信息

这时,就代表集群中不止有8000一个节点了,8000,8001已经组成了一个Redis集群,可以使用

槽指派

Redis通过分片的方式保存数据库中的键值对,集群的整个数据库被分为16384个槽,范围 0-16383,数据库的每个键都属于其中的一个槽,集群中的每个节点可以处理0或16384个槽。

上一节,8000,8001节点已经组成了一个简单的集群,但是集群还没有上线,这是因为16384个槽还没有处理任何槽。

指定槽:

127.0.0.1:8000> cluster addslots 0 1 2 3  [slot....]

通过cluster info命令查询节点槽分配信息

分配前,此时8000节点已经分配了10001个槽,8001还未分配,cluster_state的值为fail,表示集群还未进入上线状态。

127.0.0.1:8000> cluster info #查询集群信息
cluster_state:fail #集群未上线
cluster_slots_assigned:10001 #分配了10001个槽
cluster_slots_ok:10001
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:2
cluster_size:1
cluster_current_epoch:1
cluster_my_epoch:0
cluster_stats_messages_ping_sent:2872
cluster_stats_messages_pong_sent:2868
cluster_stats_messages_meet_sent:1
cluster_stats_messages_sent:5741
cluster_stats_messages_ping_received:2868
cluster_stats_messages_pong_received:2873
cluster_stats_messages_received:5741

此时给8001节点分配剩下的10001到16383个槽:

127.0.0.1:8000> cluster info #查询集群信息
cluster_state:ok #集群已上线
cluster_slots_assigned:16384 #16384个槽全部已经分配
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:2
cluster_size:2
cluster_current_epoch:1
cluster_my_epoch:0
cluster_stats_messages_ping_sent:3404
cluster_stats_messages_pong_sent:3399
cluster_stats_messages_meet_sent:1
cluster_stats_messages_sent:6804
cluster_stats_messages_ping_received:3399
cluster_stats_messages_pong_received:3405
cluster_stats_messages_received:6804

cluster_state的值为ok表明在对数据库中的16384个槽都进行指派后,集群就会进入上线状态,这时客户端就可以向集群发送数据命令了。

向集群中执行命令:

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽

1.如果数据库所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。

2.如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端(redirect)正确的节点,并再次发送之前想要执行的命令。

Redis原理详解系列-Redis集群_第9张图片

  • 假设有端口8000, 8001,8002三个节点组成的集群中,用客户端连上节点8000,并发送以下命令,那么命令会直接被8000执行:

Redis原理详解系列-Redis集群_第10张图片

因为键date所在的槽2022正是由节点8000负责处理的。

但是,如果执行以下命令,那么客户端会被转向至节点8001,然后再执行命令:

Redis原理详解系列-Redis集群_第11张图片

这是因为键aaa所在的槽6257是由节点8001负责处理的,而不是由最初接收命令的节点8000负责处理。

整个流程可以这样描述:

  • 当客户端第一次向节点8000发送SET命令的时候,节点8000会向客户端返回MOVED错误,指引客户端转向至节点8001。
  • 当客户端转向节点8001之后,客户端重定向到节点8001发送SET命令,这个命令会被节点8001成功执行。

那么,问题来了,Redis如何计算键所存放的槽呢?

计算键属于哪个槽:

节点使用以下算法来计算给定键key属于哪个槽:

CRC16(KEY) & 16383

其中CRC16(key)用于计算key的CRC-16校验和,而&16383语句用于计算出一个介于0至16383之间的整数作为key的槽号。

记录节点的槽指派信息:

clusterNode结构的slots属性和numslot属性记录了节点负责处理哪些槽

clusterNode{

// ....

unsigned char slots[16384/8]

}

slots属性是一个二进制位数组,数组的长度为16384/8=2048个字节,共16384个二进制位

举个例子:

  • 如果slots数组在索引i上的二进制位为1,那么表示节点负责处理槽i
  • 相反的,如果slots数组在索引i上的二进制位为0,那么表示节点不负责处理槽i

使用cluster keyslot 命令可以查看一个给定键属于哪个槽:

Redis原理详解系列-Redis集群_第12张图片

MOVED错误:

MOVED错误的格式为MOVED 10086 127.0.0.1:8002

表示槽10086正由IP地址为127.0.0.1,端口号为8002的节点负责。

当客户端接受到节点返回的MOVED错误之后,客户端会根据MOVED错误中携带的IP地址和端口号,转向至负责处理槽slot的节点,并向节点重新发送之前的命令,

Redis原理详解系列-Redis集群_第13张图片

节点8000向客户端返回MOVED错误

Redis原理详解系列-Redis集群_第14张图片

客户端接收到MOVED错误并转向8001节点,重新发送命令的过程

复制与故障转移

Redis集群的节点分为主节点和从节点,主节点负责处理槽,从节点负责复制某个主节点的数据,当被复制的主节点下线时,代替下线的主节点继续处理命令请求。

Redis原理详解系列-Redis集群_第15张图片

其中单圆形表示从节点,双圆形表示主节点

比如集群中包含7000,7001,7002,7003四个主节点,现在上线了两个从节点,分别是7004,7005,我们可以将7004,7005添加到集群中,并将7004,7005设置为7000的从节点。

Redis原理详解系列-Redis集群_第16张图片

集群中各个节点的状态

如果此时7000节点故障下线,那么7004,7005就会选举出一个来作为新的主节点,这个新的主节点将代替7000负责处理客户端命令和请求。

例如,7000节点下线,那么7004就会作为新的主节点负责处理槽0-5000。节点7005也由复制7000节点变为复制7004节点。

Redis原理详解系列-Redis集群_第17张图片

故障转移之后的状态

如果在故障转移完成之后,如果7000节点重新上线,那么他将成为7004的从节点。

Redis原理详解系列-Redis集群_第18张图片

主节点重新上线,被设置为从节点:

  • 当7000节点重新上线的时候,7004节点会发送cluster replicate 命令,这时7000节点会将自己的主机指针指向7004节点,以此来记录正在复制的主节点
  • 7000节点也会关闭主机标识,并且打开从机标识。
  • 一个节点成为从节点,并开始复制主节点的信息 会通过消息发送给集群中的其他节点,最终集群中的其他节点将知道某个从节点正在复制某个主节点。

故障检测

集群中的每个节点会定期向其他节点发送PING消息来判断对方的当前是否在线,如果对方在发送PING消息的指定时间之内没有回应PONG消息,那发送PING消息的节点就主观地认为对方已经进入疑似下线状态。

举个例子,假如主节点7001,7002都认为7000进入到疑似下线状态,那么主节点7001将会为7000创建下现报告。在一个集群中,半数以上的主节点都认为某个主节点x进入疑似下线状态,

那么这个主节点将会最终被标识为已下线,将主节点标记为下线的节点会向集群中广播一条x已经下线的消息,所有接收到此消息的节点都会被告知x已经处于下线状态,并标记x为已下线状态。

Redis原理详解系列-Redis集群_第19张图片

节点7001向集群中广播7000下线的消息

如图所示,主节点7002,7003都认为7000处于疑似下线状态,综合起来,7001,7002,7003都认为7000疑似已经下线,数量已经超过了半数,所以主节点7001会把7000标记为已下线状态,并向集群中广播一条7000下线的消息。

选举新的主节点

  1. 集群中有一个配置纪元的变量,初始值为0
  2. 当集群每进行一次故障转移操作的时候,配置纪元就会+1
  3. 对于每个配置纪元,集群中的主节点都有一次进行投票的机会,第一个要求主节点向自己投票的从节点将得到主节点的投票。
  4. 当集群中的从节点检测到自己复制的主节点处于下线状态时,会向集群中广播一条请求投票的消息,要求具有投票权的主节点给自己投票。
  5. 如果一个主节点在这个配置纪元中尚未投票给其他子节点,那就会投票给请求被投票支持的从节点。
  6. 每个请求投票的从节点最终会接收到自己的票数,如果集群中有N个主节点,那么当从节点接受到N/2+1个投票支持的时候,这个从节点就会成为新的主节点。因为在一个集群的配置纪元中,每个主节点只有一次投票的权利,投过一次之后,将不能再次进行投票,所以每次只能选举出一个新的主节点。

例如集群中包含7000,7001,7002,7003三个主节点,集群此时已经进行过一次故障转移操作,配置纪元当前为1,那么当7000下线时,请求投票的从节点接收到2(3/2+1)个或2个以上的票数支持后,那么这个从节点将会成为新的主节点。

总结:

  1. Redis使用多个节点来实现集群功能
  2. 使用槽指派来实现不同节点处理不同的key
  3. 向集群执行命令,节点会根据计算的槽数值,来确定到底由那个节点处理这个key
  4. 集群通过状态检测和故障转移来实现主从切换

材料整理自:

推荐书籍《Redis设计与实现 第三版》

(免费订阅,永久学习)学习地址: 

C/C++Linux服务器开发/后台架构师【公开课学习】

更多C/C++Linux相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! ! 

原文链接:https://zhuanlan.zhihu.com/p/444734866

 

你可能感兴趣的:(c++,linux,DPDK,redis,数据库,服务器,开发语言,运维)