oZaoHua 2019-12-15
安装好redis集群后,接下来记录一下它的实现中非常重要的槽道原理,在记录原理之前先对槽道进行迁移操作,直观的感受一下。
实现槽道迁移也有两种方式,一种是使用ruby的redis-trib.rb脚本,一种是使用原生的redis-cluster集群命令来完成。如果使用ruby提供的脚本,需要提前配置好,里面有坑,参考https://www.cnblogs.com/youngchaolin/p/12027448.html ,使用原生的命令则不需要配置ruby,个人感觉后者的方式可以更直观的感受迁移的过程。
现在集群有6个节点,其中有4个主2个从,现在8000节点是主但是没有槽道管理权,可以通过ruby脚本辅助迁移部分其他主节点的槽道号给它,注意现在8000节点是没有数据的,ruby脚本辅助迁移目前只能支持空槽道的迁移。
[ /home/software/redis-3.2.11]# redis-cli -p 8001 127.0.0.1:8001> cluster nodes fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576239410838 1 connected 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576239407806 5 connected 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 myself,master - 0 0 1 connected 5461-10922# 8000现在没有槽道管理 c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 master - 0 1576239407806 0 connected 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 master - 0 1576239409829 6 connected 0-5460 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576239408818 2 connected 10923-16383
(1)使用src/redis-trib.rb reshard host:port命令连接集群中任意一个节点进行操作,根据提示完成操作。
# 登录任何一个节点,准备迁移 [ /home/software/redis-3.2.11]# src/redis-trib.rb reshard 192.168.200.140:8000 >>> Performing Cluster Check (using node 192.168.200.140:8000) M: c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 slots: (0 slots) master 0 additional replica(s) M: 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 slots:0-5460 (5461 slots) master 0 additional replica(s) S: 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slots: (0 slots) slave replicates 7ce388bde879f686fc3c8491175397ca20405565 S: fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slots: (0 slots) slave replicates 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 M: 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 slots:10923-16383 (5461 slots) master 1 additional replica(s) M: 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 slots:5461-10922 (5462 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. # 输入要迁的槽道数量 How many slots do you want to move (from 1 to 16384)? 460 # 输入要迁入的节点id,输入8000节点id What is the receiving node ID? c41dbe9595ae83725d1322b032736fd198b26c49 Please enter all the source node IDs. Type ‘all‘ to use all the nodes as source nodes for the hash slots. Type ‘done‘ once you entered all the source nodes IDs. # 这里没有使用all来平均分配,而是指定源节点8003 Source node #1:2e0f23d703874db80373f28b1be8c13f9de4fe6b # 分完后点done Source node #2:done ...过程略 # 结束后需再确认一遍,输入yes Do you want to proceed with the proposed reshard plan (yes/no)? yes
(2)再次查看集群槽道分配情况,发现8000节点成功分配到所需数目的槽道。
[ /home/software/redis-3.2.11]# redis-cli -c -p 8000 -h 192.168.200.140 192.168.200.140:8000> cluster nodes# 8000成功分配到了槽道号 c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 myself,master - 0 0 7 connected 0-459 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 master - 0 1576240358713 6 connected 460-5460 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576240357702 2 connected fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576240353158 1 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576240354673 2 connected 10923-16383 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576240356693 1 connected 5461-10922
使用redis集群的命令来迁移,分为对空槽道的迁移,和有数据的槽道迁移两种。
case1.空槽道迁移
空槽道迁移相对简单一些,因为槽道没有对应数据,因此不需要考虑数据的迁移,接着上面的结果,计划将8003节点上460槽道,迁移到8000节点上去。
(1)确认460槽道上是否有数据,没有数据才迁移。使用命令cluster getkeysinslot 槽道号 查找范围。
# 460没有数据 [ /home/software/redis-3.2.11]# redis-cli -c -p 8003 -h 192.168.200.140 192.168.200.140:8003> cluster getkeysinslot 460 500 (empty list or set)
(2)登录需要导入的节点8000,执行命令cluster setslot 槽道号 importing 源节点id,将8000节点上460槽道的状态变更为importing。
[ /home/software/redis-3.2.11]# redis-cli -c -p 8000 -h 192.168.200.140 # 后面跟着源节点8003的id 192.168.200.140:8000> cluster setslot 460 importing 2e0f23d703874db80373f28b1be8c13f9de4fe6b OK # 查看460槽道的状态为导入 192.168.200.140:8000> cluster nodes c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 myself,master - 0 0 7 connected 0-459 [460-<-2e0f23d703874db80373f28b1be8c13f9de4fe6b] 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 master - 0 1576242589111 6 connected 460-5460 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576242588103 2 connected fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576242587095 1 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576242588606 2 connected 10923-16383 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576242590119 1 connected 5461-10922
(3)登录需要导出的节点8003,执行命令cluster setslot 槽道号 migrating 目标节点id,将8003节点上的460槽道的状态变更为migrating。
[ /home/software/redis-3.2.11]# redis-cli -c -p 8003 -h 192.168.200.140 # 后面跟着目标节点id 192.168.200.140:8003> cluster setslot 460 migrating c41dbe9595ae83725d1322b032736fd198b26c49 OK # 查看460槽道的状态为导出 192.168.200.140:8003> cluster nodes c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 master - 0 1576242963942 7 connected 0-459 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576242964952 5 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576242959913 2 connected 10923-16383 fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576242960919 1 connected 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 myself,master - 0 0 6 connected 460-5460 [460->-c41dbe9595ae83725d1322b032736fd198b26c49] 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576242962935 1 connected 5461-10922
(4)通知进行迁移的两个节点槽道迁移了,使用命令cluster setslot 槽道号 node 迁入节点id,需要在两个节点上均进行操作,这个时候两个节点保存槽道所有权的位序列(是一个2048位的byte数组)对应的位才会更改,8000节点上460槽道号的二进制位数字由0变成1,8001的则由1变成0。
# 迁出节点8003上执行命令,后面跟的id为迁入节点的id 192.168.200.140:8003> cluster setslot 460 node c41dbe9595ae83725d1322b032736fd198b26c49 OK 192.168.200.140:8003> quit [ /home/software/redis-3.2.11]# redis-cli -c -p 8000 -h 192.168.200.140 # 迁入节点8000上执行命令 192.168.200.140:8000> cluster setslot 460 node c41dbe9595ae83725d1322b032736fd198b26c49 OK # 查看集群节点,发现460成功迁移,后面跟的id为迁入节点的id 192.168.200.140:8000> cluster nodes c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 myself,master - 0 0 7 connected 0-460 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 master - 0 1576243857915 6 connected 461-5460 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576243859934 2 connected fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576243855393 1 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576243858926 2 connected 10923-16383 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576243860944 1 connected 5461-10922
case2.有数据的槽道迁移
上面完成了一个空槽道的迁移,如果某个槽道有数据,除了迁移槽道外,还需要将数据一起迁移。这个需要使用redis集群的命令来完成了,ruby脚本暂时不支持。本次测试迁移槽道号741,从8003节点迁移到8000节点。
(1)先确认槽道号是否不为空。命令跟上面一致,使用cluster getkeysinslot 槽道号 查找范围。
# 发现741槽道号有数据192.168.200.140:8003> cluster getkeysinslot 741 500 1) "age"
(2)登录需要导入的节点8000,执行命令cluster setslot 槽道号 importing 源节点id,将8000节点上741槽道的状态变更为importing。
[ /home/software/redis-3.2.11]# redis-cli -c -p 8000 -h 192.168.200.140 # 8000导入节点上设置741为importing,后跟源节点 192.168.200.140:8000> cluster setslot 741 importing 2e0f23d703874db80373f28b1be8c13f9de4fe6b OK # 741状态变更 192.168.200.140:8000> cluster nodes c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 myself,master - 0 0 7 connected 0-460 [741-<-2e0f23d703874db80373f28b1be8c13f9de4fe6b] 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 master - 0 1576245822658 6 connected 461-5460 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576245819635 2 connected fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576245821146 1 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576245821651 2 connected 10923-16383 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576245820642 1 connected 5461-10922
(3)登录需要导出的节点8003,执行命令cluster setslot 槽道号 migrating 目标节点id,将8003节点上的741槽道的状态变更为migrating。
[ /home/software/redis-3.2.11]# redis-cli -c -p 8003 -h 192.168.200.140 # 8003导出节点上设置741为migrating,后跟目标节点id 192.168.200.140:8003> cluster setslot 741 migrating c41dbe9595ae83725d1322b032736fd198b26c49 OK # 741状态变更 192.168.200.140:8003> cluster nodes c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 master - 0 1576245954877 7 connected 0-460 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576245960923 5 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576245961931 2 connected 10923-16383 fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576245957902 1 connected 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 myself,master - 0 0 6 connected 461-5460 [741->-c41dbe9595ae83725d1322b032736fd198b26c49] 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576245959917 1 connected 5461-10922
(4)登录源节点8003,将741槽道对应的数据(一个个的key-value),以及槽道保存key信息的map({槽道号:[age,...]}),一并迁入到8000节点,使用如下命令完成。
#migrate host port key| destination-db timeout [COPY] [REPLACE] [KEYS key] # "" 表示key的匹配 # 0 代表0号database # 1000代表超时毫秒 # keys 后面跟的是具体的key名称,这里只有一个age 192.168.200.140:8003> migrate 192.168.200.140 8000 "" 0 1000 keys age OK
(5)通知进行迁移的两个节点槽道迁移了,到了这一步这个和上面空槽道的迁移操作一样。
# 通知迁出节点8003 192.168.200.140:8003> cluster setslot 741 node c41dbe9595ae83725d1322b032736fd198b26c49 OK 192.168.200.140:8003> quit [ /home/software/redis-3.2.11]# redis-cli -c -p 8000 -h 192.168.200.140 # 通知迁入节点8000 192.168.200.140:8000> cluster setslot 741 node c41dbe9595ae83725d1322b032736fd198b26c49 OK # 查看741节点已经迁移到了8000节点 192.168.200.140:8000> cluster nodes c41dbe9595ae83725d1322b032736fd198b26c49 192.168.200.140:8000 myself,master - 0 0 7 connected 0-460 741 2e0f23d703874db80373f28b1be8c13f9de4fe6b 192.168.200.140:8003 master - 0 1576246974768 6 connected 461-740 742-5460 2c52d95c3d6d4c396469a81edfc1493d984e0f2d 192.168.200.140:8005 slave 7ce388bde879f686fc3c8491175397ca20405565 0 1576246973758 2 connected fd4c160edc74536d79ea29d239dca43275ec6b5a 192.168.200.140:8004 slave 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 0 1576246969725 1 connected 7ce388bde879f686fc3c8491175397ca20405565 192.168.200.140:8002 master - 0 1576246971741 2 connected 10923-16383 231fe9df31dc1ccf7cca5ae2fb2313979cd6fa83 192.168.200.140:8001 master - 0 1576246972750 1 connected 5461-10922 # 查看数据发现无需重定向,因此数据也迁移成功 192.168.200.140:8000> get age "18"
关于槽道原理的说明,网上博文很多,下面参考博文以及《Redis设计与实践》,记录一下自己的理解。
当随便连接redis集群中一个已知的节点想通过set key value命令来保存数据,或get key来获取数据,首先会根据key值,通过算法计算值%16384的取余结果,算出这个key应该落入的槽道号,那接下来就只需要找到哪个节点管理这个槽道号,就可以在这个节点操作了。
上面的情况有两种可能,要么当前连接的节点就管理着这个槽道,在当前节点直接set就可以了。要么这个节点不管理这个槽道号,需要寻找对的节点。如果想判断槽道是否归某个节点管理,就需要使用一个能标记的东西将槽道和节点联系起来,在redis集群中,有两个关键类以及里面的两个关键属性(clusterNode和clusterState的slots属性),来提供上述逻辑判断的依据,如下:
(1)clusterNode类的slots属性:clusterNode类对应一个节点的信息,类似cluster nodes命令得到的单个节点的信息,包括节点id、ip、端口等信息,它有一个关键属性slots,类型是一个2048位的byte数组,对应就是16384个bit位,通过这个数组bit是1或0,来决定节点是否拥有槽道的管理权,每个节点都有自己的这个数组,并且还有其他节点的数组信息,如下图①所示。
(2)clusterState类的slots属性:clusterState类记录了集群状态的信息,类似cluster info命令得到的集群信息。它有一个关键的属性slots,类型是一个16384位长度的clusterNode数组,它记录着整个集群的槽道信息,根据这个数组的下标,可以找到对应的节点,如下图②所示。
(3)clusterState类的nodes属性:这个属性是一个字典类型的数据,key为节点的id,value为clusterNode对象,通过这个属性可以得到cluster集群中所有节点的信息。不过这个属性跟槽道没有直接关系,但是在理解上面两个属性同时存在必要性时会有帮助,如下图③所示。
到了这里,上面的两种情况就可以解决了,通过key值计算后的取余结果,可以在当前节点的clusterNode对象的slots数组,找到对应的bit位的值,如果是1就说明当前节点拥有这个槽道的管理权,可以set数据,如果是0就需要寻找正确的节点,这个时候就需要查找当前节点clusterState对象的slots数组,依然通过刚计算出的取余结果作为下标,来找到对应的节点,找到后重定向到正确的节点,就将剩下的判断交给正确的节点来处理了,依然需要判断一遍槽道管理权。
另外,节点中是否只需要clusterNode或clusterState对象中的一个slots是否也可以?
(1)如果只有clusterNode的slots,当想判断某个槽道是否有分配出去,如果这个节点分配出去了还好,如果没有分配出去,则需要将所有节点的clusterNode对象都遍历一遍才能确认。因为通过某个clusterNode上对应槽道的标识0,还不能确认是否真的没有分配,还需要确认下一个节点,结果下一个节点也是0,这样就需要遍历完每个节点,对应的时间复杂度为O(n),n就是上面字典的key的数目。如果有clusterState,只需要通过下标指向的内容是否为Null,就可以判断出来,时间复杂度为O(1)。
(2)如果只有clusterState的slots,为了将某个节点如8000所管的槽道信息发送给另外一个节点如8001,则需要循环遍历一遍clusterState.slots数组得到结果后才能发送出去,这比单独发送一个8000节点的clusterNode.slots数组显得低效。
以上就是对redis集群中槽道迁移的记录,以及槽道原理的简单说明,后续继续完善。
参考博文
(1)《Redis设计与实践》槽道原理