redis cluster 重新分片

我们在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所示。

                                                                                  图1 nodes-7000.conf文件

                                                                                 图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 importing <7000_runid>,表示节点7200准备从节点7000接收槽16198的所有权。观察nodes-7000.conf和nodes-7100.文件,发现没有任何的变化;但是nodes-7200.conf发生了变化,如图4。通过redis-cli -c -p 7200命令以集群的模式连接到节点7200,执行cluster nodes命令,命令的输出结果和nodes-7200.conf文件出现了同样的提示,如图5所示。图中的箭头表示节点7200准备接收节点7000编号为16198的槽,这是clusterState_7200.importing_slots_from[16198]值的反应。nodes-7000.conf和nodes-7100.conf没有任何变化,在这两个节点的客户端执行cluster nodes命令的输出结果和初始状态没有任何变化,说明节点7000和节点7100对当前要迁移槽16198这件事情一无所知。

这一步操作也就是在节点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 migrating <7200_runid>,nodes-7100.conf和nodes-7200.conf文件的内容,以及通过节点7100的客户端和7200的客户端(以后分别用client_7000、client_7100和client_7200表示节点7000、节点7100和节点7200在内存中的客户端)执行cluster nodes命令的输出结果和第1步相比,仍然没有发生任何的变化。但是,nodes-7000.conf和通过client_7000执行cluster nodes命令的输出结果却出现了变化,分别如图6和图7所示。通过对比可知,界定啊7100对于16198槽的迁移仍然是一无所知,节点7000知道自己将要把槽16198迁移到节点7200,节点7200也知道这件事情。

这一步操作,就是将内存中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命令的输出

redis cluster 重新分片_第1张图片

                                                                    图13 在client_7100执行get is命令的输出

                                                                   图14 在client_7200执行get is命令的输出

下面分别对12、图13和图14进行解释。由于转移没有完成(也就是,我们没有对节点执行cluster setslot node 命令>,集群中所有的节点的clusterState.slots[16198]仍然是指向自身的clusterState.nodes中的7000节点。

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所示。

redis cluster 重新分片_第2张图片

                                                           图15 为客户端添加asking标识之后执行get is命令。

redis cluster 重新分片_第3张图片

                                                                   图8.16 迁移槽的过程中执行命令的判断流程

但是,如果我们通过client_7000、client_7100和client_7200执行get love命令。节点7000能够直接执行该命令,节点7100和节点7200会分别产生MOVED错误,然后转向节点7000执行该命令,如图17、图18和图19所示。所以在槽转移的过程中,只是已经被转移的键需要客户端的asking标识,未转移的键的访问不受影响。

redis cluster 重新分片_第4张图片

                                                                           图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 node <7200_runid>,那么输出分别如图20、图21和图22所示。

                                            图20 对节点7000执行cluster setslot node <7200_runid>命令

                                           图21 对节点7100执行cluster setslot node <7200_runid>命令

                                           图22 对节点7200执行cluster setslot node <7200_runid>命令

因为,节点7000知道自己负责的槽16198还有键love没有转移到节点7200,因此节点7000知道转移并未完成,所以报错,如图20所示。如果对节点7100执行上述命令 ,可以成功执行 ,但是此时执行get is命令,仍然输出和图13一样的结果,这是为什么,难道执行cluster setslot node <7200_runid>之后产生的MOVED错误不应该是跳转到127.0.0.1:7200吗???等看过代码之后详细解释或者有哪位大神在这里给解释一下。如果对7200节点执行同样的命令,那么执行可以成功,如图22所示。在对节点7200成功执行cluster setslot node <7200_runid>后如果通过client_7000、client_7100和client_7200执行get is命令,那么输出如图23、图24和图25所示。

如果对节点7200执行cluster setslot node <7200_runid>命令,那么即使只是对节点7200执行cluster setslot node <7200_runid>就可以是使对is键的访问回归正常,且此时观察nodes-7000.conf、nodes-7100.conf和nodes-7200.conf,分别如图26、图27和图28所示,发现槽16198已经归节点7200负责。

                                                                      图23 在client_7000重新执行get is命令

                                                                      图24 在client_7100重新执行get is命令

redis cluster 重新分片_第5张图片

                                                                      图25 在client_7200重新执行get is命令

                        图26 只是对节点7200执行cluster setslot node <7200_runid>之后,nodes-7000.conf文件

                        图27 只是对节点7200执行cluster setslot node <7200_runid>之后,nodes-7100.conf文件

                        图28 只是对节点7200执行cluster setslot node <7200_runid>之后,nodes-7200.conf文件

                        图29 对节点7000执行cluster setslot node <7200_runid>之后,nodes-7000.conf文件

从图26、图27和图28可以看出,虽然只是对节点7200执行了cluster setslot node <7200_runid>,但是系统中所有的节点都更新了自己对槽16198的认知,都知道当前槽16198已经归节点7200负责,这是通过集群中节点之间发送的消息进行传递的。节点之间传递的消息clusterMsg的头部有unsigned char myslots成员用于通知其他节点自己所负责的槽。节点7200执行cluster setslot node <7200_runid>命令的结果就是将clusterState_7200.slots[16198]的值变为clusterState_7200.myself的值,将clusterState_7200.nodes中的clusterNode_7200.slots[16198]置为1,clusterState_7200.nodes中的clusterNode_7000.slots[16198]清零,同时将clusterState_7200.importing_slots_from[16198]置为NULL,所以nodes-7200.conf中已经没有了引入槽16198的标识。

当节点7200执行完cluster setslot node <7200_runid>命令之后,节点7200通知节点7100和节点7000自己负责的槽。节点7000立即更新自己的clusterState_7000.slots[16198]的值指向clusterState_7000.nodes中clusterNode_7200结构,并将clusterState_7000.nodes中clusterNode_7200.slots[16198]的值置为1,clusterState_7000.myself中的slots[16198]置为0;节点7100更新clusterState_7100.slots[16198]的值指向clusterState_7100.nodes中的clusterNode_7200,并将clusterState_7100.nodes中clusterNode_7000.slots[16198]位置为0,clusterNode_7200.slots[16198]位置为1。

上面操作完成之后,集群中的节点7000、节点7100和节点7200都知道槽16198属于节点7200。但是从nodes-7000.conf文件之后,节点7000并没有将clusterState_7000.migrating_slots_to[16198]置为NULL,因此还存在迁出槽16198到7200节点的标识。只有对节点7000执行cluster setslot node <7200_runid>命令之后,槽16198的迁移标识消失,如图29所示。

 

槽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命令

redis cluster 重新分片_第6张图片

                                                        图32 非完全迁移之后client_7200执行get love命令

也就是说,当没有迁移完所有键就对目标节点执行了cluster setslot node ,会造成数据的丢失。具体的等看过源代码之后再解释吧。

 

6、如果没有迁移完所有的键,对节点7000执行cluster setslot node <7200_runid>,会得到如图20所示的结果。但是如果迁移完槽16198中所有的键之后,对节点7000执行cluster setslot node <7200_runid>,此时查看nodes-7000.conf、nodes-7100.conf和nodes-7200.conf,分别如图33、图34和图35所示。

                         图33 对节点7000执行cluster setslot node <7200_runid>之后,nodes-7000.conf文件

                        图34 对节点7000执行cluster setslot node <7200_runid>之后,nodes-7100.conf文件

                       图35 对节点7000执行cluster setslot node <7200_runid>之后,nodes-7200.conf文件

从图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所示。

redis cluster 重新分片_第7张图片

                        图36 client_7000节点执行cluster setslot node <7200_runid>之后,get is命令执行结果

redis cluster 重新分片_第8张图片

                        图37 client_7100节点执行cluster setslot node <7200_runid>之后,get is命令执行结果

redis cluster 重新分片_第9张图片

                        图38 client_7200节点执行cluster setslot node <7200_runid>之后,get is命令执行结果

从上述的分析可以得出,节点7000、节点7100和节点7200对于槽16198的归属权没有做到同步,所以形成了死循环,此时只需要对节点7200执行cluster setslot node ,即可破解死循环,将归属权真正给与7200。经验证的确是这样。

以上是个人对于redis集群重新分片过程的理解,欢迎吐槽。

你可能感兴趣的:(redis)