我们在IP地址127.0.0.1已经创建好了三个节点,端口分别是7000、7100和7200,以下分别用7000、7100和7200表示这三个节点,这三个节点的配置文件中的配置选项cluster-config-file分别为nodes-7000.conf、nodes-7100.conf和nodes-7200.conf。初始化状态下,集群状态配置分别如图1、2和3所示。
图2 nodes-7100.conf文件
图3 nodes-7200.conf文件
从图1、2和3中可以看出节点7000负责槽0-5499以及16198;节点7100负责槽5500-10999;节点7200负责槽11000-16197、16199-16383。现在,我们要将槽16198迁移到7200节点。那么,在迁移的过程中节点的状态配置文件nodes-xxxx.conf以及在上述3个客户端执行cluster nodes命令的输出结果是怎样的呢?
1、对7200执行cluster setslot
这一步操作也就是在节点7200对应的clusterState(以后节点7000、节点7100和节点7200在内存中的结构clusterState分别用clusterState_7000、clusterState_7100和clusterState_7200进行区分)结构的importing_slots_from成员中将importing_slots_from[16198]的值从NULL变为指向clusterState_7200.nodes中代表节点7000的clusterNode(为了方便节点7000、节点7100和节点7200在内存中的clusterNode结构也分别用clusterNode_7000、clusterNode_7100和clusterNode_7200表示)。但是槽16198的所有权仍然是归节点7000所有,内存中clusterState_7200.slots[16198]的值仍然是指向clusterState_7200.nodes中clusterNode_7000的,并且cluterState_7200.nodes中节点clusterNode_7000.slots[16198]仍然为1。
图4 执行cluster setslot 16198 importing <7000_runid>之后,nodes-7200.con文件
图5 执行cluster setslot 16198 importing <7000_runid>之后,client_7200的cluster nodes命令输出结果
2、对节点7000执行cluster setslot
这一步操作,就是将内存中clusterState_7000.migrating_slots_to[16198]的值变为指向clusterState_7000.nodes中的clusterNode_7200,且clusterState_7000.myself.slots[16198]的值仍然为1,表示节点7000对于槽16198仍然占据拥有权。
图6 cluster setslot 16198 migrating <7200_target_id>之后,node-7000.conf文件
图7 cluster setslot 16198 migrating <7200_target_id>之后,client_7000的cluster nodes命令的输出
总之,经过1、2步骤之后,只是clusterState_7000.migrating_slots_to[16198]变为指向clusterState_7000.nodes的clusterNode_7200的指针;clusterState_7200.importing_slots_from[16198]变为指向clusterState_7200.nodes的clusterNode_7000的指针。
3、在7000的客户端执行cluster getkeysinslot 16198 5,输出如图8所示。 getkeysinslot slot count命令用于获取slot中count个键。redis中为了能够获取在同一个slot中的所有key,在redis-4.0.1版本中的clusterState结构中加入了rax *slots_to_keys成员保存该信息。
图8 cluster getkeysinslot 1698 5命令的输出
此时还没有开始对槽中的数据进行转移,因此 ,槽16198的所有数据还都存在于节点7000,并且整个集群中的所有节点都认为槽16198的所有权归于节点7000,无论是在集群中的哪一个节点中执行get is 或者get love(节点7000除外,该节点执行上述两个命令会直接返回),最终都会通过MOVED错误告知client重新连向127.0.0.1:7000,只是通过redis-cli -c -p 7000命令以集群模式连接节点,MOVED错误不显示,如图9和10所示。但是,如果以redis-cli -p 7000命令连接7000客户端,那么此时会打印出MOVED错误,如图11所示。
图9 在7100的客户端执行get is命令,被转向到7000节点
图10 在7200的客户端执行get is命令,被转向到7000节点
图11 非集群模式下执行get is命令的输出
4、首先,将槽16198的is键转入到节点7200,在7000节点执行migrate 127.0.0.1 7200 is 0 5命令,此时nodes-xxxx.conf中的数据没有任何的变化。如果此时,分别在client_7000、client_7100和client_7200分别执行get is命令,那么将会产生ASK错误,输出分别如图12、图13和 图14所示。
图12 在client_7000执行get is命令的输出
图13 在client_7100执行get is命令的输出
图14 在client_7200执行get is命令的输出
下面分别对12、图13和图14进行解释。由于转移没有完成(也就是,我们没有对节点执行cluster setslot
client_7000执行get is命令,节点7000计算is属于槽16198,通过对比clusterState_7000.slots[16198] = clusterState_7000.myself,说明正是自己负责槽16198。但是,此时发现is键在槽16198中并不存在,然后检查clusterState_7000.migrating_slots_to[16198]发现指向clusterState_7000.nodes中的clusterNode_7200,因此根据clusterNode_7200中记录的ip和port产生ASK错误,提示用户连接到127.0.0.1:7200去执行get is命令。如果,此时槽没有进行转移,那和正常的集群执行命令的结果一样(如果键对应的槽由自己负责,则处理命令;否则,产生MOVED错误)。
client_7100执行get is命令,节点7100计算槽is键属于16198且发现clusterState_7100.slots[16198]指向clusterState_7100.nodes中的clusterNode_7000节点,此时首先产生MOVED错误。client_7000连接的时候添加了-c选项,所以直接连接到127.0.0.1:7000重新执行get is命令,然后重复和直接在client_7000执行get is同样的过程。
client_7200执行get is命令和client_7100执行该命令是同样的结果。
根据上面的结果,好像我们在键转移的过程中 ,我们无法获取已经转移了键的值,虽然这个键只是在集群中转移了存储位置,并没有被删除。为了解决这个问题,redis为客户端设置了ASKING标识。当出现ASK错误的时候,根据ASK提示的ip+port,通过redis-cli -c -p 手动连接到127.0.0.1:7200节点,首先为客户端设置asking标识,然后执行get is命令,发现此时可以得到键is的值,如图15所示。但是随后再一次执行get is命令,发现和图14的结果是一致的,这是因为asking标识是一个一次性标识,使用过后client的asking标识就已经消失了。转移槽的过程中命令的执行过程如图16所示。
图15 为客户端添加asking标识之后执行get is命令。
图8.16 迁移槽的过程中执行命令的判断流程
但是,如果我们通过client_7000、client_7100和client_7200执行get love命令。节点7000能够直接执行该命令,节点7100和节点7200会分别产生MOVED错误,然后转向节点7000执行该命令,如图17、图18和图19所示。所以在槽转移的过程中,只是已经被转移的键需要客户端的asking标识,未转移的键的访问不受影响。
图17 client_7000执行get love命令
图18 client_7100执行get love命令
图19 client_7100执行get love命令
5、当前槽16198中存在is和love两个键,而我们只转移了一个is键。如果,此时对节点7000、节点7100和节点7200执行cluster setslot
图20 对节点7000执行cluster setslot
图21 对节点7100执行cluster setslot
图22 对节点7200执行cluster setslot
因为,节点7000知道自己负责的槽16198还有键love没有转移到节点7200,因此节点7000知道转移并未完成,所以报错,如图20所示。如果对节点7100执行上述命令 ,可以成功执行 ,但是此时执行get is命令,仍然输出和图13一样的结果,这是为什么,难道执行cluster setslot
如果对节点7200执行cluster setslot
图23 在client_7000重新执行get is命令
图24 在client_7100重新执行get is命令
图25 在client_7200重新执行get is命令
图26 只是对节点7200执行cluster setslot
图27 只是对节点7200执行cluster setslot
图28 只是对节点7200执行cluster setslot
图29 对节点7000执行cluster setslot
从图26、图27和图28可以看出,虽然只是对节点7200执行了cluster setslot
当节点7200执行完cluster setslot
上面操作完成之后,集群中的节点7000、节点7100和节点7200都知道槽16198属于节点7200。但是从nodes-7000.conf文件之后,节点7000并没有将clusterState_7000.migrating_slots_to[16198]置为NULL,因此还存在迁出槽16198到7200节点的标识。只有对节点7000执行cluster setslot
槽16198由节点7000负责的时候,本来存在两个键is和love,但是执行完上述的步骤之后,分别在client_7000和client_7100和client_7200执行get love命令,结果如图30、图31和图32所示,发现键love丢失了。
图30 非完全迁移之后client_7000执行get love命令
图31 非完全迁移之后client_7100执行get love命令
图32 非完全迁移之后client_7200执行get love命令
也就是说,当没有迁移完所有键就对目标节点执行了cluster setslot
6、如果没有迁移完所有的键,对节点7000执行cluster setslot
图33 对节点7000执行cluster setslot
图34 对节点7000执行cluster setslot
图35 对节点7000执行cluster setslot
从图33可以看出,节点7000认为槽16198已经归节点7200所有,此时在内存中clusterState_7000.slots[16198]指向clusterState_7000.nodes中clusterNode_7200节点,clusterState_7000.migrating_slots_to[16198]为NULL,clusterState_7000.myself.slots[16198]清零。但是,从图34可以看出节点7100认为槽16198仍然归节点7000所有,其clusterState_7100.slots[16198]指向clusterState_7100.nodes中clusterNode_7000节点且clusterState_7100.nodes的clusterNode_7000.slots[16198]为1。从图35可以看出,节点7200认为槽16198还是归节点7000所有,clusterState_7200.slots[16198]指向clusterState_7200.nodes的clusterNode_7000且clusterState_7200.nodes的clusterNode_7000.slots[16198]仍然为1,且clusterState_7200.importing_slots_from[16198]指向clusterState_7200.nodes的clusterNode_7000节点。
因此,当在client_7000执行get is命令时,节点7000节点此时槽已经归节点7200节点拥有且槽16198并不在迁移的过程中(clusterState_7000.migrating_slots_to[16198]为NULL),因此产生MOVED错误转向节点7200。节点7200仍然认为槽16198归节点7000所有(clusterState.slots[16198]指向clusterState_7200.nodes中的clusterNode_7000节点),因此get is命令在节点7000和节点7200之间来会产生MOVED错误,形成死循环,如图36所示。对节点7100执行get is命令,会有同样的结果,从节点7100->节点7000->节点7200->节点7000.......,循环往复,如图37所示;同理对节点7200也是如此,如图38所示。
图36 client_7000节点执行cluster setslot
图37 client_7100节点执行cluster setslot
图38 client_7200节点执行cluster setslot
从上述的分析可以得出,节点7000、节点7100和节点7200对于槽16198的归属权没有做到同步,所以形成了死循环,此时只需要对节点7200执行cluster setslot
以上是个人对于redis集群重新分片过程的理解,欢迎吐槽。