redis集群是redis提供的分布式方案,集群通过分片(sharding)来进行数据共享并提供复制和故障转移.
节点
1 CLUSTER MEET <ip> <port> 添加该节点到现在登录节点所在的集群里 -- 握手过程
2 redisServer会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式.
3 节点会继续单机模式下的redis服务器的组件.就是多了一些关于集群的内容.
4 clusterNode结构保存了一个节点当前的状态.比如:节点的创建时间,节点的名字,节点当前的配置纪元,节点的IP地址,端口号等.
5 clusterNode结构的link属性是一个clusterlink结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输人缓冲区和输出缓冲区:
6 每个节点都保存着一个clusterState结构,这个结构记录了当前节点的视角下,集群目前所处的状态,比如:集群是在线还是上线,集群包含多少节点,集群目前的配置详情
7 CLUSTER MEET命令的实现(发送给A,A将B加入到集群中)1 节点A将为B创建一个clusterNode结构,并将该结构添加到自己的clusterstate.nodes字典里面
2 节点A根据命令给定的ip端口,向节点B发送MEET消息
3 节点B收到A发送的消息,节点B会为节点A创建一个clusterNode结构,也加到自己的clusterstate.nodes字典里面
4 节点B向A返回一条PONG消息
5 节点A收到B返回的PONG消息,
6 节点A向B返回一条PING消息
7 节点B收到节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功接收到自己返回的PONG消息.握手完成.
8 之后节点A将节点B的信息通过Gossip协议(流传病)传播给集群中的其他节点.让其他节点也与B进行握手,最后,一段时间后,节点B会被集群中的所有节点认识.
槽指派
1 redis集群的整个数据库被分为16384个槽.当数据库的16384个槽都有节点在处理时,集群处于上线状态.相反的,则是下线状态.
2 共有两个数组记录了槽的指派信息. clusterstate.slots数组记录了集群中所有槽的指派信息,值为clusterNode. 而clusterNode.slots数组只记录了clusterNode结构所代表的节点的槽指派信息.值为0或者1.
clusterstate.slots可以快速定位槽i指派到了那个节点中.复杂度为O(1).clusterNode.slots数组能保证每次将节点的槽指派消息传播给其他节点.而不需要遍历整个槽指派信息.
3 clusterNode结构的slots属性和numslot属性记录了节点负责处理那些槽,slots属性是一个二进制位数组,长度为16348/8=2048个字节.一共包含16384个二进制位.
索引I的值为1的则表示节点负责处理槽I.相反,则不负责.值指向一个clusterNode结构,代表槽已经指派给clusterNode结构所代表的节点.指向null.则表示未分配.
numslot属性则记录了节点负责的槽数量.
4 集群中的每个节点都会将自己的slots数组通过消息发送给集群中的其他节点,最后都知道了
5 CLUSTER ADDSLOTS <sort...> 命令的实现1 先循环所有输入的槽,确定这些槽是否分配过,如果有分配过,直接返回错
2 灭有的话,设置clusterState的slots数组对应的槽指向当前节点.
3 将clusterNode结构的slots数组对应的索引(槽)的值设置为1
在集群中执行命令
1 计算键属于哪个槽? "CRC16(key) & 16383" CRC16(key)用于计算键key的CRC16校验和,而 & 16383语句则用于计算出一个介于0~16383之间的整数.作为槽号.
2 判断槽是否是当前节点负责处理.根据clusterstate.slots[i]的值是否等于clusterstate.myself,是,则直接执行,如果不是,则MOVED错误.指引客户端转向槽所在的节点
3 一个集群客户端通常会与集群中多个节点创建套接字连接,节点转向实际上就是换一个套接字来发送命令.
4 集群模式下(redis-cli -c -p 6379) 接收到MOVED错误时,并不会打印出错误,而是根据MOVED错误自动进行节点转向.单机模式下,MOVED错误就打印出来了,因为它不清楚MOVED错误的作用.
5 节点会使用clusterState结构中的slots_to_keys跳跃表来保存槽与键之间的关系..快速定位到键所在的槽.
重新分片:将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点,并且源槽上的键值对数据也会移动.
主要使用redis-trid集群管理软件完成. redis提供了重新分片所需的所有命令,这个工具通过向源节点与目标节点发送命令来进行重新分片
实现过程:
1 对目标节点发送CLUSTER SETSLOT <slot> IMPORTING <source_id>,让目标节点准本好从源节点导入属于槽slot的键值对
2 对源节点发送CLUSTER SETSLOT <slot> MIGRATING <target_id>,让源节点准备好迁移到目标节点.
3 向源节点发送CLUSTER GETKEYSINSLOT <slot> <count> ,获取最多count个属于槽slot的键值对的键名
4 步骤3获得的数据, MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>,将数据从源节点移动到目标节点.
5 重复3,4步骤,知道所有的数据都迁移完成
6 向集群中的任意一个节点发送 CLUSTER SETSLOT <slot> NODE <target_id>.这个重新指派消息会通过消息发送到整个集群,最终大家都知道了
ASK错误
在分片期间,源节点向目标节点迁移槽的过程中:一些键值对已经迁移到目标节点了,有些还在源节点上.
当客户端发送一个key的操作命令,当key还在源节点上时,直接执行就可以.不在就需要返回一个ASK错误了.指引客户端去目标节点执行.
ASK错误与MOVED错误一样,在集群模式下,会自动转换.单机客户端模式下,则报错.
原理:1 CLUSTER SETSLOT IMPORTING 命令可以将clusterState.importing_slots_from[i]的值设置为source_id所代表节点的clusterNode结构.(没有导入的情况下,这个数组指向null)修改后意味着i这个槽正在导入clusterNode代表的节点的槽.
2 CLUSTER SETSLOT MIGRATING 命令则修改了clusterState.migrating_slots_to[i]的值为目标节点代表的clusterNode结构.
3 此时收到一个关于key的命令请求,如果键key所属的槽就指派了这个节点的话,会在自己的数据库查找键,找到了直接执行.没有找到.会检测自己的clusterState.migrating_slots_to数组, 看键key所属的槽是否正在迁移,如果正在迁移的话.会返回一个ask错误.引导客户端到正在导入槽i的节点去查找键key.
4 在转向到目标节点去查key之前.要先在客户端执行ASKING命令,只有一个目的:打开该客户端的REDIS_ASKING标识.
5 节点接受到key,槽不属于自己节点,都会返回MOVED错误.只有在clusterState.importing_slots_from数组中发现这个槽在导入到自己节点,同时,客户端的REDIS_ASKING标识是打开的,才会在自己的节点执行.
复制与故障转移
Redis的节点分为主节点与从节点. 其中,主节点处理槽,从节点则用于处理复制某个主节点,并在复制的主节点下线时,由其他主节点选举产生(如果是多个从节点),代替下线主节点继续处理命令请求.
复制逻辑:1 CLUSTER REPLICATE <node_id> 可以让接受命令的节点成为node_id所指定节点的从节点,并开始对主节点进行复制.
2 接收到该命令的节点,首先会在自己的clusterState.nodes字典中找到node_id指针对应节点的ClusterNode结构,并将自己的clusterState.myself.slaveof指针指向这个结构,以此来记录这个节点正在复制主节点
3 节点修改自己的clusterState.myself.flags中的属性.关闭原来的REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识.表示这个节点已经由原来的主节点变成了从节点.
4 节点会调用复制代码,并根据clusterState.myself.slaveof指向的clusterNode结构保存的ip地址和端口号,对主节点进行复制.复制功能与单机服务器使用相同的代码,功能也一样.
相当于向从节点发送命令SLAVEOF <master_ip> <master_port>
5 一个节点成为从节点,并开始复制某个主节点的消息会通过消息发送给集群中的其他节点,最终集群中de其他节点就会知道某个从节点正在复制某个主节点.
6 集群中的所有节点都会在代表主节点的clusterNode结构的slaves属性和numslaves属性中记录正在复制这个主节点的从节点名单故障检测:
1 集群内每个节点都会定期向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果没有在规定时间内接收到PONG消息,那就会标记为疑似下线(PFAIL).
首先会在自己的clusterState.nodes字典里找到未回复消息对应的clusterNode结构,并在结构的flags属性中打开REDIS_NODE_PFAIL标识,表示疑似下线.
2 集群中各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息.当A收到B认为C疑似下线状态时,A会在自己的clusterState.nodes字典里,找到C对应的clusterNode结构.
并将主节点B的下线报告添加到clusterNode结构的fail_report链表里.
3 当一个集群里面,半数以上负责处理槽的主节点都将某个主节点报告为疑似下线,那么这个节点将被标记为已下线(FAIL).
4 将节点标记为已下线的这个节点会将这个下线消息向集群广播,所有收到这条消息的节点都会将这个节点标记为已下线.故障转移(选举):TODO
1
面试题收集:
1 是否使用过 Redis 集群,集群的原理是什么?
2 Redis 集群方案什么情况下会导致整个集群不可用?
3 Redis 哈希槽的概念?
4 Redis 集群的主从复制模型是怎样的?
5 Redis 集群会有写操作丢失吗?为什么?
6 Redis 集群之间是如何复制的?
7 Redis 集群最大节点个数是多少?
8 Redis 集群如何选择数据库?