1. 程式人生 > >Redis 學習筆記(十五)Redis Cluster 叢集擴容與收縮

Redis 學習筆記(十五)Redis Cluster 叢集擴容與收縮

Redis Cluster 叢集伸縮

1. 伸縮原理

Redis提供了靈活的節點擴容和收縮方案。在不影響叢集對外服務的情況下,可以為叢集新增節點進行擴容也可以對下線節點進行縮容。

我們在Redis Cluster 介紹與搭建這篇文章中搭建了一個三主三從Redis叢集(如下圖所示)。在搭建 Redis Cluster 通訊流程剖析這篇部落格中根據原始碼詳細剖析了搭建叢集的流程。

這裡寫圖片描述

本篇部落格要講的是,Redis叢集的擴容和縮容過程。

127.0.0.1:6379> cluster nodes
29978c0169ecc0a9054de7f4142155c1ab70258b 127.0
.0.1:6379 myself,master - 0 0 7 connected 0-5461 8f285670923d4f1c599ecc93367c95a30fb8bf34 127.0.0.1:6380 master - 0 1496717082785 3 connected 5462-10922 66478bda726ae6ba4e8fb55034d8e5e5804223ff 127.0.0.1:6381 master - 0 1496717085793 2 connected 10923-16383 961097d6be64ebd2fd739ff719e97565a8cee7b5 127.0.0.1:6382 slave 29978
c0169ecc0a9054de7f4142155c1ab70258b 0 1496717084791 7 connected 6fb7dfdb6188a9fe53c48ea32d541724f36434e9 127.0.0.1:6383 slave 8f285670923d4f1c599ecc93367c95a30fb8bf34 0 1496717087797 4 connected e0c7961a1b07ab655bc31d8dfd583da565ec167d 127.0.0.1:6384 slave 66478bda726ae6ba4e8fb55034d8e5e5804223ff 0 1496717086795 2 connected

對應的主節點負責的槽位資訊,如下圖所示:

這裡寫圖片描述

2. 擴容叢集

擴容叢集是分散式儲存最常見的需求,Redis叢集擴容可以分為如下步驟:

  • 準備新節點
  • 加入叢集
  • 遷移槽和資料

2.1 準備新節點

我們需要兩個節點,埠分別為63856386,配置和之前叢集節點配置基本相同,除了埠不同,以便於管理。6385節點配置如下:

port 6385                               //埠
cluster-enabled yes                     //開啟叢集模式
cluster-config-file nodes-6385.conf     //叢集內部的配置檔案
cluster-node-timeout 15000              //節點超時時間,單位毫秒
// 其他配置和單機模式相同

啟動兩個節點

sudo redis-server conf/redis-6385.conf
sudo redis-server conf/redis-6386.conf

啟動後的新節點會作為孤兒節點執行,沒有和其他節點與之通訊。

2.2 加入叢集

我們可以通過CLUSTER MEET命令將6385節點加入到叢集中。

127.0.0.1:6379> CLUSTER MEET 127.0.0.1 6385
OK
127.0.0.1:6379> CLUSTER NODES
cb987394a3acc7a5e606c72e61174b48e437cedb 127.0.0.1:6385 master - 0 1496731333689 8 connected
......

也可以使用redis專門進行叢集管理的工具redis-trib.rb,位於Redis的原始碼目錄中,把6386節點加入到叢集中

sudo src/redis-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379
127.0.0.1:6379> CLUSTER NODES
cdfb1656353c5c7f29d0330a754c71d53cec464c 127.0.0.1:6386 master - 0 1496731447703 0 connected
......

這兩種方法可以,新加入的節點都是主節點,因為沒有負責槽位,所以不能接受任何讀寫操作,對於新加入的節點,我們可以有兩個操作:

  • 為新節點遷移槽和資料實現擴容。
  • 作為其他主節點的從節點負責故障轉移。

2.3 遷移槽和資料

當我們將新節點加入集群后,我們就可以將槽和資料遷移到新的節點,遷移的方法也有兩種,可以使用redis-trib.rb工具,也可以通過手動命令的方式,但是一般要確保每個主節點負責的槽數是均勻的,因此要使用redis-trib.rb工具來批量完成,但是我們只是為了演示遷移的過程,所以接下來手動使用命令進行遷移。

我們先建立幾個屬於一個槽的鍵,將這些鍵遷移到新的節點中。

127.0.0.1:6379> SET key:{test}:555 value:test:555
-> Redirected to slot [6918] located at 127.0.0.1:6380
OK
127.0.0.1:6380> SET key:{test}:666 value:test:666
OK
127.0.0.1:6380> SET key:{test}:777 value:test:777
OK
127.0.0.1:6380> CLUSTER KEYSLOT key:{test}:555
(integer) 6918
127.0.0.1:6380> CLUSTER KEYSLOT key:{test}:666
(integer) 6918
127.0.0.1:6380> CLUSTER KEYSLOT key:{test}:777
(integer) 6918

本來在6379節點中建立,但是重定向到了6380節點中,因為我們常見的鍵根據CRC16演算法計算分配到了6918槽中,而這個槽由6380節點負責。

如果鍵的名字中帶有{},那麼計算雜湊值時就只計算{}包含的字串,所以建立的三個鍵屬於一個槽。

計算雜湊值的原始碼如下:

unsigned int keyHashSlot(char *key, int keylen) {
    int s, e; /* start-end indexes of { and } */
    // 找'{'字元
    for (s = 0; s < keylen; s++)
        if (key[s] == '{') break;
    // 沒有找到"{}",直接計算整個key的雜湊值
    if (s == keylen) return crc16(key,keylen) & 0x3FFF;
    // 找到'{',檢查是否有'}'
    for (e = s+1; e < keylen; e++)
        if (key[e] == '}') break;
    // 沒有找到配對的'}',直接計算整個key的雜湊值
    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
    // 如果找到了"{}",計算{}中間的雜湊值
    return crc16(key+s+1,e-s-1) & 0x3FFF;
}

我們已經獲取了要遷移的槽,是6918。因此,流程如下:

  • 目標6385節點中,將槽6918設定為匯入狀態
127.0.0.1:6385> CLUSTER SETSLOT 6918 importing 8f285670923d4f1c599ecc93367c95a30fb8bf34
OK
// 8f285670923d4f1c599ecc93367c95a30fb8bf34 是 6380 節點的名字

目標6385節點中,檢視槽6918匯入狀態

127.0.0.1:6385> CLUSTER NODES
cb987394a3acc7a5e606c72e61174b48e437cedb 127.0.0.1:6385 myself,master - 0 0 8 connected [6918-<-8f285670923d4f1c599ecc93367c95a30fb8bf34]
  • 6380節點中,將槽6918設定為匯出狀態
127.0.0.1:6380> CLUSTER SETSLOT 6918 migrating cb987394a3acc7a5e606c72e61174b48e437cedb
OK
// cb987394a3acc7a5e606c72e61174b48e437cedb 是 6385 節點的名字

6380節點中,檢視槽6918匯出狀態

127.0.0.1:6380> CLUSTER NODES
8f285670923d4f1c599ecc93367c95a30fb8bf34 127.0.0.1:6380 myself,master - 0 0 3 connected 5462-10922 [6918->-cb987394a3acc7a5e606c72e61174b48e437cedb]
  • 批量獲取槽6918中的鍵
127.0.0.1:6380> CLUSTER GETKEYSINSLOT 6918 5
1) "key:{test}:555"
2) "key:{test}:666"
3) "key:{test}:777"

確認一下這三個鍵是否存在於源6380節點。

127.0.0.1:6380> MGET key:{test}:777 key:{test}:666 key:{test}:555
1) "value:test:777"
2) "value:test:666"
3) "value:test:555"
  • 執行migrate命令進行遷移
127.0.0.1:6380> MIGRATE 127.0.0.1 6385 "" 0 1000 keys key:{test}:777 key:{test}:666 key:{test}:555
OK

批量遷移版本的MIGRATE命令是在redis 3.0.6之後加入的,命令引數如下:

MIGRATE host port key dbid timeout [COPY | REPLACE]
MIGRATE host port "" dbid timeout [COPY | REPLACE] KEYS key1 key2 ... keyN
// host port 指定遷移的目的節點地址
// dbid 指定遷移的資料庫id
// timeout 遷移的超時時間
// 如果指定了 COPY 選項,表示不刪除源節點上的key
// 如果指定了 REPLACE 選項,替換目標節點上已存在的key(如果存在)

當遷移完成後,我們在源6380節點查詢這三個鍵,傳送回覆了一個ASK錯誤

127.0.0.1:6380> MGET key:{test}:777 key:{test}:666 key:{test}:555
(error) ASK 6918 127.0.0.1:6385

最後,我們只需向任意節點發送CLUSTER SETSLOT <slot> NODE <target_name>命令,將槽指派的資訊傳送給節點,然後這個節點會將這個指派資訊傳送至整個叢集。

CLUSTER SETSLOT 6918 node cb987394a3acc7a5e606c72e61174b48e437cedb
// cb987394a3acc7a5e606c72e61174b48e437cedb 是 6385 節點的名字

6381節點執行命令

127.0.0.1:6381> CLUSTER SETSLOT 6918 node cb987394a3acc7a5e606c72e61174b48e437cedb
OK

6379節點檢視當前叢集槽指派資訊

127.0.0.1:6379> CLUSTER NODES
29978c0169ecc0a9054de7f4142155c1ab70258b 127.0.0.1:6379 myself,master - 0 0 7 connected 0-5461
66478bda726ae6ba4e8fb55034d8e5e5804223ff 127.0.0.1:6381 master - 0 1496736248776 2 connected 10923-16383
cb987394a3acc7a5e606c72e61174b48e437cedb 127.0.0.1:6385 master - 0 1496736244766 10 connected 6918
8f285670923d4f1c599ecc93367c95a30fb8bf34 127.0.0.1:6380 master - 0 1496736247773 3 connected 5462-6917 6919-10922
// 過濾掉從節點和未指派槽的主節點

可以看到6380節點負責的槽變為5462-6917 6919-10922,而6918已經被6385節點負責了。

  • 新增從節點

開始的時候,我們加入了兩個新節點到叢集中,節點6385已經遷移了槽位和資料作為主節點,但是該節點還不具有故障轉移的能力。

此時,還需要將6386節點作為6385節點的從節點,從而保證叢集的高可用。使用cluster replicate <master_id>命令為主節點新增從節點,叢集模式下不支援slaveof命令。

127.0.0.1:6386> CLUSTER REPLICATE cb987394a3acc7a5e606c72e61174b48e437cedb
OK
127.0.0.1:6386> CLUSTER NODES
cb987394a3acc7a5e606c72e61174b48e437cedb 127.0.0.1:6385 master - 0 1496742992748 10 connected 6918
cdfb1656353c5c7f29d0330a754c71d53cec464c 127.0.0.1:6386 myself,slave cb987394a3acc7a5e606c72e61174b48e437cedb 0 0 0 connected

到此就完成了叢集的擴容。叢集關係如下圖所示:

這裡寫圖片描述

3. 收縮叢集

收縮叢集以為著縮減規模,需要從叢集中安全下線部分節點。需要考慮兩種情況:

  • 確定下線的節點是否有負責槽,如果是,需要把槽遷移到其他節點,保證節點下線後整個槽節點對映的完整性。
  • 當下線節點不在負責槽或著本身是從節點時,就可以通知叢集內其他節點忘記下線節點,當所有節點忘記該節點後就可以正常關閉。

我們這次使用redis-trib.rb工具來下線遷移槽。流程和擴容叢集非常相似,正好方向相反,將6380變為目標節點,6385成了源節點。將剛才新擴容的叢集收縮回去。

./redis-trib.rb reshard 127.0.0.1:6385
>>> Performing Cluster Check (using node 127.0.0.1:6385)
......
[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)? 1 /*遷移一個槽*/
// 目標節點的id
What is the receiving node ID? 8f285670923d4f1c599ecc93367c95a30fb8bf34 /*輸入目標`6380`節點的id*/
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 表示所有節點都是源節點
// done 表示輸入完成
Source node #1:cb987394a3acc7a5e606c72e61174b48e437cedb
Source node #2:done
.....
// 是否立即執行重新分片計劃
Do you want to proceed with the proposed reshard plan (yes/no)? yes
Moving slot 6918 from 127.0.0.1:6385 to 127.0.0.1:6380: ...

檢視一下結果:

127.0.0.1:6380> CLUSTER NODES
8f285670923d4f1c599ecc93367c95a30fb8bf34 127.0.0.1:6380 myself,master - 0 0 11 connected 5462-10922
cb987394a3acc7a5e606c72e61174b48e437cedb 127.0.0.1:6385 master - 0 1496744498017 10 connected

6380節點已經接管了6385節點的槽。

最後讓叢集所有的節點忘記下線節點6385。執行CLUSTER FORGET <down_node_id>或者使用工具。

./redis-trib.rb del-node 127.0.0.1:6379 cdfb1656353c5c7f29d0330a754c71d53cec464c
>>> Removing node cdfb1656353c5c7f29d0330a754c71d53cec464c from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
./redis-trib.rb del-node 127.0.0.1:6379 cb987394a3acc7a5e606c72e61174b48e437cedb
>>> Removing node cb987394a3acc7a5e606c72e61174b48e437cedb from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

注意,先下線從節點,在下線主節點,以免不必要的全量複製操作。對6379節點做忘記下線節點的操作,那麼經過一段時間,叢集中的其他節點也都會忘記。

127.0.0.1:6380> CLUSTER NODES
6fb7dfdb6188a9fe53c48ea32d541724f36434e9 127.0.0.1:6383 slave 8f285670923d4f1c599ecc93367c95a30fb8bf34 0 1496744890808 11 connecte
29978c0169ecc0a9054de7f4142155c1ab70258b 127.0.0.1:6379 master - 0 1496744892814 7 connected 0-5461
66478bda726ae6ba4e8fb55034d8e5e5804223ff 127.0.0.1:6381 master - 0 1496744891810 2 connected 10923-16383
e0c7961a1b07ab655bc31d8dfd583da565ec167d 127.0.0.1:6384 slave 66478bda726ae6ba4e8fb55034d8e5e5804223ff 0 1496744888804 2 connected
8f285670923d4f1c599ecc93367c95a30fb8bf34 127.0.0.1:6380 myself,master - 0 0 11 connected 5462-10922
961097d6be64ebd2fd739ff719e97565a8cee7b5 127.0.0.1:6382 slave 29978c0169ecc0a9054de7f4142155c1ab70258b 0 1496744889805 7 connected

6380埠的主節點已經忘記了下線節點,因此下線節點已經安全的下線。