【Redis】Redis集群架构剖析(4):槽位迁移,重新分配

  在前一篇Redis集群架构剖析中,我们了解了一个集群如何处理一个由redis-cli发来的指令,但是都是在cluster槽位不变的情况下。那为什么槽位会变呢?集群有可能增删节点,在第二篇的时候,我们知道只有所有节点都分配到槽位的时候,redis cluster在是online状态。在开始之前,依旧可以先思考下面的问题:

  • 集群是否要下线才能重新分配槽位呢?
  • 如果不需要下线就要实现槽位的重新分配,需要怎么做呢?
  • 迁移过程中会不会有指令发过来呢?有发过来,有需要怎么处理呢?

  先不卖关子,集群在重新分配的过程中,不需要下线,并且源节点和目标节点都可以继续处理命令请求。下面我们来看下redis是如何实现的。

重新分配

  重新分配的操作就是将任意数量已经指派给某个节点(源节点)的槽位改指派给另一个节点(目标节点),并且相关槽位所属的键值对也会从源节点移动到目标节点。

  举个例子,下图原本由6370,6371和6372组成的集群,现在加入一个新的节点6373。那么原本分配给6372的槽位1000116383,就将其中的1500116383槽位重新分配给节点6373。重新分配的动作在CLUSTER MEET这个6373节点的时候就做完了。

在这里插入图片描述

重新分配的实现过程

  Redis cluster的重新分配操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供进行重新分配所需的命令,redis-trib则通过向源节点和目标节点发送指令来进行重新分配的操作。

  下图是对一个槽位重新分配的一个流程,值得注意的是里面的第三和第四步,先迁移value再迁移key,这个在后面会有用处。

在这里插入图片描述

  1. 首先redis-trib对目标节点发送指令,让目标节点准备好从源节点导入属于槽slot的键值对,指令如下:

    CLUSTER SETSLOT <slot> IMPORTING <source_id>

  2. 然后redis-trib对源节点发送指令,让源节点准备好将属于slot的键值对迁移到目标节点,指令如下:

    CLUSTER SETSLOT <slot> MIGRATING <target_id>

  3. 这时候因为源节点收到了命令,要准备将slot的键值对迁移给目标节点。但不是所有要迁移的slot上都已经存储了键值对,所以接着

  4. 如果这个slot上面有存储键值对的话,redis-trib会向源节点发送指令,获得最多count个属于槽slot的键值对的键名,指令如下:CLUSTER GETKEYSINSLOT <slot> <count> 。接着redis-trib对每个键名,向源节点发送MIGRATE <target_ip> <target_port> <key_name> 0 <timeout> 命令,将被选中的键值对,从源节点迁移到目标节点

  5. 如果这个slot不存在键值对,或者经过了步骤4,那么redis-trib会向集群中的任意一个节点发送CLUSTER SETSLOT <slot> NODE <target_id>的命令。将槽slot指派给目标节点的信息,发送至整个集群,最终集群终端中的所有节点都会知道槽slot已经指派给了目标节点。

  如果这个slot有存储多个键值对,就会重复执行步骤4里面的第二个指令和步骤5。

ASK错误

  在迁移过程中,很有可能有redis-cli发请求过来请求数据,这个时候应该怎么做呢?可以联想一下上一篇,如果请求到不是本节点的槽位,节点会告诉redis-cli应该去哪个节点找到对应的槽位,这个思路是否也可以借鉴呢?其实这个问题,在我们设计分布式系统的时候还是很重要的,要想到这种特殊的情况,要嘛是直接禁止访问,要不然就是设计一个机制,可以让迁移和请求同时存在。显然,redis选择了后者。

  当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:

  • 源节点会现在自己的数据库里面查找指定的键,如果找得到的话,就直接执行客户端的命令
  • 如果找不到的话,这个键有可能已经被迁移到了目标节点,源节点就会向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令(是不是跟MOVED很像)

  下图就是节点收到请求后是否要发送ASK的流程图

在这里插入图片描述

  这个ASK和MOVED一样的返回,也是返回Redirected到某个节点,如果需要看到ASK错误的话,得用单机redis请求。

SETSLOT数据结构

  在细究ASK的实现细节前,我们先看下cluster是用什么数据结构来记录,那些槽位在源节点,哪些又正在迁移到目标节点。

  在重新分配的实现过程中,我们知道最开始有两个动作,分别是目标节点准备导入槽,源节点准备将槽导出,这设计到两个指令,分别也对应着两个数据结构

CLUSTER SETSLOT IMPORTING

  clusterState结构的importing_slots_from数组记录了当前节点正在从其他节点导入的槽:

typedef struct clusterState {
    //...
    clusterNode *importing_slots_from[16384];
    //...
} clusterState;

  如果importing_slots_from[i]不为NULL,而是指向一个clusterNode结构,那么表示正在从这个clusterNode节点导入槽i。

  举个例子,加入6373加入集群,然后将6372上的15002重新分配给6373,会执行CLUSTER SETSLOT 15002 IMPORTING 6372的节点ID

  那么6373的importing_slots_from就会变成下图这样,也就是重新分配实现过程的第一步,6373的importing_slots_from[15002]会指向节点6372

在这里插入图片描述

CLUSTER SETSLOT MIGRATING

  clusterState结构的migrating_slots_to数组记录了点前节点正在迁移至其他节点的槽:

typedef struct clusterState{
    //...
    clusterNode *migrating_slots_to[16384];
    //...
} clusterState;

  如果migrating_slots_to[i]不为NULL,而是指向一个clusterNode结构,那么表示正在导入到这个clusterNode节点。

  举个例子,接着上面的importing,到了重新分配实现过程的第二步,给6372发送指令CLUSTER SETSLOT 15002 MIGRATING 6373的节点ID,那么6372的migrating_slots_to会变成如下图所示:

在这里插入图片描述

ASKING

  在前面了解到如果请求的命令对应的键不在源节点上,在迁移的目标节点上,源节点就会返回一个ASK错误。接到ASK错误的客户端就会根据错误提供的IP地址和端口号,转向正在导入槽的目标节点,然后首先会想目标节点发送一个ASKING命令,之后才会再重新发送原本想要执行的命令。下图是一个简单的转向后,请求ASKING的示意图。

在这里插入图片描述

  ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识,以下是这个命令实现的伪代码:

def ASKING();
	// 打开标识
	client.flags != REDIS_ASKING
	
	// 向客户端返回OK
	reply("OK")

  回想一下,之前槽位不存在请求节点的时候,节点会向客户端返回一个MOVED错误。但是,如果节点的clusterState.importing_slots_from[i]显示节点正在导入槽i,并且发送命令的客户端带有REDIS_ASKING标识,那么节点将魄力执行这个关于槽i的命令一次,看一下流程图:

在这里插入图片描述

  当客户端接收到ASK错误并转向到正在导入槽的节点时,客户端会先向节点发送一个ASKING命令,然后才重新发送想要执行的命令,这是因为如果客户端不发送ASKING命令,而直接发送想要执行的命令的话,客户端发送的命令将被节点拒绝执行,并返回MOVED错误。

  举个例子,在上面的例子中,我们向6373节点请求15002槽,因为15002是在导入槽,所以如果我们没有发送一个ASKING的命令,6373会返回一个MOVED的错误,并转到6372,因为槽15002还分配在6372上。如果在请求之前,发送了ASKING命令,那么6373就会执行这个命令。

注意:REDIS_ASKING标识是一次性标识,当节点执行了一个带有REDIS_ASKING标识的客户端发送的命令之后,客户端的REDIS_ASKING标识就移除了。

ASK错误和MOVED错误

这两个错误都会导致客户端转向,那他们区别如下:

  • MOVED错误代表槽的所属节点已经从一个节点转移到另一个节点,客户端每次收到MOVED时都会直接将请求发送给指向的节点
  • ASKING错误只是两个节点在迁移槽的过程中使用的一种零时措施。

  这篇文档,了解到节点发生槽转移时,集群是如何处理重新分配的,数据结构又是如何存储的。这个是针对数据的一种异常情况,还有一个是针对节点的异常,比如说我部署的redis节点挂掉了,原本存的槽即使知道导向这个节点,但这个节点也没有回复的能力了。那我们该怎么做呢?是不是该备份一下这个数据呢?似乎就是我挂了,你顶我。针对这个一个异常行为,我们下节分析。


系列文章

  1. 【Redis】Redis集群架构剖析(1):认识cluster
  2. 【Redis】Redis集群架构剖析(2):槽位
  3. 【Redis】Redis集群架构剖析(3):集群处理redis-cli指令
  4. 【Redis】Redis集群架构剖析(5):复制与故障转移
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值