安裝好redis集群后,接下來記錄一下它的實現中非常重要的槽道原理,在記錄原理之前先對槽道進行遷移操作,直觀的感受一下。
槽道遷移
實現槽道遷移也有兩種方式,一種是使用ruby的redis-trib.rb腳本,一種是使用原生的redis-cluster集群命令來完成。如果使用ruby提供的腳本,需要提前配置好,里面有坑,參考https://www.cnblogs.com/youngchaolin/p/12027448.html ,使用原生的命令則不需要配置ruby,個人感覺后者的方式可以更直觀的感受遷移的過程。
ruby腳本輔助遷移
現在集群有6個節點,其中有4個主2個從,現在8000節點是主但是沒有槽道管理權,可以通過ruby腳本輔助遷移部分其他主節點的槽道號給它,注意現在8000節點是沒有數據的,ruby腳本輔助遷移目前只能支持空槽道的遷移。
[root@node01 /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命令連接集群中任意一個節點進行操作,根據提示完成操作。
# 登錄任何一個節點,准備遷移 [root@node01 /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節點成功分配到所需數目的槽道。
[root@node01 /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-cluster命令遷移
使用redis集群的命令來遷移,分為對空槽道的遷移,和有數據的槽道遷移兩種。
case1.空槽道遷移
空槽道遷移相對簡單一些,因為槽道沒有對應數據,因此不需要考慮數據的遷移,接着上面的結果,計划將8003節點上460槽道,遷移到8000節點上去。
(1)確認460槽道上是否有數據,沒有數據才遷移。使用命令cluster getkeysinslot 槽道號 查找范圍。
# 460沒有數據 [root@node01 /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。
[root@node01 /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。
[root@node01 /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 [root@node01 /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。
[root@node01 /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。
[root@node01 /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 [root@node01 /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設計與實踐》槽道原理