REDIS CLUSTER 搭建,擴容縮容基本原理


摘要

在redis4.0.14版本,是通過ruby的工具redis-trib.rb工具進行擴容縮容以及集群搭建的工作,然后到redis5.0后取消了這個工具的功能並合並到redis-cli中,這里就讓我們了解一下redis-trib.rb工具在搭建集群和擴容縮容中到底做了什么把

源碼在github 上搜索redis,第一個就是了,這里就不貼代碼了

1. Redis4.0 不使用redis-trib.rb工具搭建cluster

1.1 redis redis-trib.rb create方法分析

步驟 作用
node ClusterNode.new(n) 初始化,n是node,對節點信息初始化
node.connect(:abort => true) 連接節點
node.assert_cluster 判斷節點信息,看cluster_enabled是否開啟
node.load_info 如果節點已經有slot,則對該節點執行addslot命令,設置節點的cluster info信息
node.assert_empty

驗證該節點的cluster_known_nodes是否為1 和驗證 該節點的db0是否為空

如果cluster_known_nodes不為1則認為已經加入其他集群,如果db0不為空則認為該節點不為空不能加入該集群

add_node(node) @nodes << node 把該node節點加入@nodes數組
把所有節點按以上流程走一遍,再開始執行下面
check_create_parameters

masters @nodes.length/(@replicas+1) 計算master 節點,@replicas代表每個主有多少個從

如果是一主二從的集群,master的結點數=用節點數/2+1

所以一主二從的集群起碼要9個節點,才能搭建cluster

alloc_slots

先維護一個字典,每個node的ip為鍵,所有的nodes的信息為值(一個字典),把nodes加入字典中

ips[n.info[:host]= [if !ips[n.info[:host]]

ips[n.info[:host]] << n

選擇命令的前3個作為master節點

計算每個每個節點分配多少槽(ClusterHashSlots.to_f / masters_count)

然后調用addslots方法,其實就是把要分配的slots 遍歷加入節點的對象(一個字典)中的key[slots]中,每個master節點都執行一個分配槽的操作

show_nodes 輸出槽的分配結果

 

1.2 搭建過程

## step 1

## 以第一點的配置啟動6個節點

## redis-server /usr/conf/cluster/7000.conf

## redis-server /usr/conf/cluster/7001.conf

## .....................................................................

## redis-server /usr/conf/cluster/7005.conf

 

## step 2

## redis-cli -p 7001 -c   (隨便進入一個集群)

## 執行cluster meet / redis-trib.rb add nodes 把其他節點加入集群

 

## 查看集群的節點信息

## cluster nodes

 

 

## step 4

## 挑選前3個節點作為master

##  16384 / 3 = 5461.333 

## 先給第一個master幾點分配 5461個節點

##  redis-cli -p 7001 -c cluster addslots {0..5460}

 

 

## 第二個節點分配的位置的初始位置為5461,結束為5461+5461 =10922

## redis-cli -p 7003 -c cluster addslots {5461..10922}

 

## 第三個節點分配的位置的初始位置為10923,last = 16384-1

## redis-cli -p 7000 -c cluster addslots {10923..16838}

 

 

 

## step 5

## 給master 節點設置slave節點

## 分別進入3個沒有槽的節點 執行cluster replicate

## redis-cli -p 7002 -c cluster replicate 9222d226718a707c4517e634dfe38e81c026986c

## redis-cli -p 7005 -c cluster replicate 403b709820b1deeea8eab0744c76f928b53b7e86

## redis-cli -p 7004 -c cluster replicate 7bca1a86e03c7c176a8298c34ff363b7100d21d8

 

 

## step 6 

## 查看每個節點的集群狀態

## redis-cli -p 7000 -c cluster info

## redis-cli -p 7001 -c cluster info

## redis-cli -p 7002 -c cluster info

 

 ## 確認每個節點的集群狀態,因為加入A節點的槽部分不可用,A的cluster_state的狀態會為fail,其他節點的clutser_state會顯示ok

2.Redis4.0 不使用redis-trib.rb工具對集群進行擴容縮容

2.1 redis-trib.rb reshard 方法

ALL的分配方式順序
作用

遍歷所有節點,過濾掉slave的節點,和接收槽的節點

把剩余的節點加入sources數組中,作為槽的來源節點

 

多個槽來源的計算方式:

1.先對來源槽進行排序,

2 計算每個節點要移出多少個槽 (numslots.to_f/source_tot_slots*s.slots.length)

這樣的處理方式會帶來最終分配的slot與請求遷移的slot數量不一致

3. 計算出每個節點需要移出的槽數

4. 遍歷每個節點的所需要移出的槽,加入move數組中

move = []

move << {:source=>s,slot=>slot}

要移動的槽全部放進數組中

numslots.to_f 總共要移動的槽數

source_tot_slots 所有來源節點的槽數

s.slots.length 單個來源節點擁有的槽數

沒記錯是入棧出棧的計算方式

先計算numslots.to_f/source_tot_slots

再算 *s.slots.length

target.r.cluster("setslot",slot,"importing",source.info[:name])

source.r.cluster("setslot",slot,"migrating",target.info[:name])

migrate ...遷移鍵

n.r.cluster("setslot",slot,"node",target.info[:name])

每個槽使用importing導出,然后再移動到接收槽上

源節點和目標節點都執行cluster setslot ... node ...

 

2.2 擴容介紹

## step 1

## redis-server 啟動兩個節點,一個主一個從

## redis-server /usr/conf/cluster/7006.conf

## redis-server /usr/conf/cluster/7007.conf

## netstat -lnp | grep redis

## 新啟動了兩個節點

 

## step 2

## 進入集群中的任意帶槽的節點節點

## redis-cli -p 7003 -c

## 把新啟動的節點加入集群

## cluster meet 127.0.0.1 7006

## cluster meet 127.0.0.1 7007

## cluster nodes

 

## 成功把兩個節點加入集群

 

## step 3 擴容

## 擴容,給新的一個master 節點分配槽

## 多個來源節點的情況

## 遍歷每個節點,計算每個節點應該移出多少個槽 (numslots.to_f/source_tot_slots*s.slots.length)

## numslots.to_f 總共要移動的槽數

## source_tot_slots 所有來源節點的槽數

## s.slots.length 單個來源節點擁有的槽數

## 循環每個slot:

## 接收節點執行 cluster setslot <slot> importing <node_id>   (node_id為源節點id)

## 來源節點執行 cluster setslot <slot> migrating <node_id>    (node_id為接收節點id)

## 源節點執行 cluster countkeysinslot <slot> 查看要移動的槽 有沒有鍵

## 如果有 則 繼續執行 cluster getkeysinslot <slot> <count>  查看slot節點的多少個key值, 如果上一步槽的鍵數量為空跳過此步驟

## 然后執行 MIGRATE 源節點ip 源節點port "" 0 5000 KEYS key1 key2 key3  把鍵先遷移,執行這條命令后該鍵不可讀不可寫

## 分別在源節點和接收節點執行 cluster setslot <slot> node <node_id>   

## 現在看 主要在接收節點上執行 cluster setslot ...node ...命令,只有在接收節點上面執行后會跟新接收節點的epoch 版本號,然后發消息跟其他節點說該slot已經歸我管了,如果epoch比接收信息的節點高,接收信息的節點會跟新該slot的狀態

## 而源節點上面執行cluster setslot .... node ...命令只移除mirating flag 並不會更新版本號

## 那么問題來了,那importing 和migrating 步驟不就可以省略嗎,那源節點也沒必要執行cluster setslot ... node ...命令了(其實並不是這樣,詳見測試)

 

2.3 擴容實例

把7373 節點從 7004 移動到7009

 

## step 1

## redis-cli -p 7009 -c cluster setslot 7373 importing cd8cd114215e4c2869d6d31ae7fe5f5cff922ed3

## 從其他節點觀察槽信息

## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 從7009 節點上看到設置了一個importing flag (-<-)

 

## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 在7004 上面沒有變化

 

## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 在7003 上面沒有變化

 

##往槽7373 插入數據

 

 ## 說明槽還在 7004 上面

 

## step 2

## redis-cli -p 7004 -c cluster setslot 7373 migrating 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## 從其他節點觀察槽信息

## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

## 7009 上面仍然只看到自己身上只有一個importing flag,7373槽仍然在7004 上面

 

## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2 

## 7004 上面看到自己多出一個mirating flag,7373槽仍然在7004 上面

 

## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 7003 仍然沒變化

 

## redis-cli -p 7003 -c get gw 

 

 ## 7373槽仍在在7004上面

 

## step 3

## redis-cli -p 7004 -c cluster countkeysinslot 7373

## 查看槽7373 有多少個key,必須在源節點上面執行

 

## 7373 上面有一個key

 

 

## step 4

## redis-cli -p 7004 -c cluster getkeysinslot 7373 1

 

## 7373槽上面只有一個key gw,必須在源節點上面執行

 

## step 5

## redis-cli -p 7004 -c MIGRATE 127.0.0.1 7009 "" 0 5000 KEYS gw

## 把槽7373的鍵從7004 移動到 7009

## redis-cli -p 7004 -c get gw

## redis-cli -p 7004 -c set gw qwes

 

## 執行完migrate 后鍵也不可訪問

## 因為執行 setslot migrating 和setslot importing之后,舊的鍵可以在源節點上訪問,但是新的鍵設置只能在新節點上面執行asking后執行設置新鍵命令才能成功

## 這樣保證了 setslot migrating 和setslot importing之后不會在源節點上設置新鍵值對,避免setslot ,,,node,,, 后新鍵值丟失

## 由於mirate命令命令把舊鍵移動到接收節點上去,所以現在源節點上已經沒有舊鍵了,這個時候,所有鍵的訪問都必須在接收節點上執行asking后執行操作

## 例如:

## redis-cli -p 7009 -c

## asking

## set  sand aksnd

## 才會成功

 

## step 6

## redis-cli -p 7004 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2

## 從7004節點上面看,原本7004 上面的migrating flag 沒有了,7373槽已經移動到 7009 上面去

 

## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

 

 

## 從 7009 上面看 importing flag 還沒被消除, 7373slot 仍然在7004上面

## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 從7003 上面看 7373 仍然在 7004 上面

 

## get gw

## set gw asd

 

## 在 7003和7004 上面反復橫跳

##注意,這里先在源節點上面執行setslot.... node....命令

## 形成原因

 

 ## 無論從哪個節點上面訪問都會導致這種情況,這是由於每個節點信息不同步 或者說數據不一致導致的

 

## step 7

## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## 從其他節點查看slot7373的信息

## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 從7009節點上面看,原本7009上面的importing flag 沒有了,7373槽已經移動到 7009 上面去,遷移成功

 

## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 從7004 節點上面看,7373slot 已經遷移成功

 

## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

 

 ## 從7003上面看,7373slot已經遷移成功

 

## get gw 

## set ge asds

 

 ## 數據已經可讀可寫

 ## 反復橫跳現象已經消失,只要先在接收節點上面執行setslot ... node  就不會出現反復橫跳現象

 

## step 8

## 觀察日志 發現 只有接收slot 的節點 執行setslot ... node 命令 才會更新epoch 版本號,其他節點不會更新epoch,

 

## 節點之間通信的時候,會帶着epoch版本號

## 加入 節點A接收了槽7373,更新了版本號到57,然后通信時A 給節點B發消息,說7373槽已經歸我管了

## 現在有兩種情況,一是節點B的epoch 比57 高,二是節點B的epoch版本號比57低

## 情況一:不理會節點A 的信息,這就導致數據不一致,節點B仍然認為槽7373 歸源節點管

## 情況二: 更新信息,同步認為槽7373已經歸節點A 管

 

3.cluster 操作命令

cluster info :打印集群的信息
cluster nodes :列出集群當前已知的所有節點( node),以及這些節點的相關信息。
節點
cluster meet <ip> <port> :將 ip 和 port 所指定的節點添加到集群當中,讓它成為集群的一份子。
cluster forget <node_id> :從集群中移除 node_id 指定的節點。
cluster replicate <master_node_id> :將當前從節點設置為 node_id 指定的master節點的slave節點。只能針對slave節點操作。
cluster saveconfig :將節點的配置文件保存到硬盤里面。
槽(slot)
cluster addslots <slot> [slot ...] :將一個或多個槽( slot)指派( assign)給當前節點。
cluster delslots <slot> [slot ...] :移除一個或多個槽對當前節點的指派。
cluster flushslots :移除指派給當前節點的所有槽,讓當前節點變成一個沒有指派任何槽的節點。
cluster setslot <slot> node <node_id> :將槽 slot 指派給 node_id 指定的節點,如果槽已經指派給
另一個節點,那么先讓另一個節點刪除該槽>,然后再進行指派。
cluster setslot <slot> migrating <node_id> :將本節點的槽 slot 遷移到 node_id 指定的節點中。
cluster setslot <slot> importing <node_id> :從 node_id 指定的節點中導入槽 slot 到本節點。
cluster setslot <slot> stable :取消對槽 slot 的導入( import)或者遷移( migrate)。

cluster keyslot <key> :計算鍵 key 應該被放置在哪個槽上。
cluster countkeysinslot <slot> :返回槽 slot 目前包含的鍵值對數量。
cluster getkeysinslot <slot> <count> :返回 count 個 slot 槽中的鍵 

 

4. cluster setslot命令的作用

4.1 cluster setslot <slot> migrating <node_id>

測試1:

## step 1

## 測試:執行腳本對slot批量操作(把腳本中除了setslot migrating命令注釋掉,只執行 cluster setslot <slot> migrating <node_id>):

## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7004 127.0.0.1:7009

## 把 7009 中 8000個slot 設置migrating flag 指向7004

 

## 盡管在7004 上面先執行asking 后 再set 也不行

 

 

 

## 因為7004 上面沒有設置importing flag的原因,不允許新的鍵在7004上面執行

## 但是加上cluster setslot <slot> importing <node_id> 就沒問題,詳細在測試6中

## 所以只加上migrating flag 不加上importing flag 沒辦法設置新值

 

測試2:

## step 1

## set madasdsaasdsd lasmd

## set madasdscxzaasdsd lasmad

 

 

## step 2 

## 測試:執行腳本對slot批量操作(把腳本中除了setslot migrating命令注釋掉,只執行 cluster setslot <slot> migrating <node_id>): 

## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7009 127.0.0.1:7004

## 把 7004 中 8000個slot 設置migrating flag

 

## step 3

## get madasdsaasdsd

## get madasdscxzaasdsd

 

 ## 結合實驗一說明 處於migrating flag狀態的slot 都不能存放新值 但可以獲取和設置舊值

 

4.2 cluster setslot <slot> importing <node_id>

測試3:

## step 1

## 對接收節點直接執行 cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## 執行直接遷移到源節點

## 觀察日志發現沒有生成新的epoch 版本號

## 所以其他節點也不會同步該變更信息

## 說明可能沒有執行cluster setslot <slot> importing <node_id> 給遷移的槽設置importing flag的話不會生成新的epooch版本號

 

測試4:

## step 1

## 接收節點先執行: redis-cli -p 7009 -c cluster setslot 7373 importing cd8cd114215e4c2869d6d31ae7fe5f5cff922ed3  (給slot7373 設置importing flag)

## 接收節點執行: redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## 生成了新的epoch版本號,同時 變更也會被其他節點應用

 

 

 

 

 

 ## cluster setslot <slot> importing <node_id> 語句第一個作用是讓cluster setslot... node 命令生成新的epoch版本號

 

測試5

## step 1

## 測試:執行腳本對slot批量操作(把腳本中除了setslot importing命令注釋掉,只執行 cluster setslot <slot> importing <node_id>):

## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7009 127.0.0.1:7004

## 把 7004 中 8000個slot 設置importing flag

## 單獨執行這個命令沒有什么影響,讀取和新設置的鍵都會繼續使用原來的節點來存、

 

測試6

## step 1

## 測試:執行腳本對slot批量操作(把腳本中除了setslot importing 和 miragting 以外命令注釋掉,只執行 cluster setslot <slot> importing <node_id> 和 cluster setslot <slot> migrating <node_id> ):

## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7004 127.0.0.1:7009  (移動的槽數量 target  source)

##  7004 是目標節點, 7009 是源節點

## redlis-cli -p 7004 -c

## set asdk asmdzmc

 

 

 

## 可測試1 情況一樣,仍然設置不了值,這個error 意思是請去7004 中執行操作?

## redis-cli -p 7004 -c

## asking

## set asdk qnwien

 

 

4.3 cluster setslot <slot> node <node_id>

## 那所有master節點或者所有節點都執行一次,不就可以了嗎,理論上對所有節點所擁有的集群信息都修改一次,是否可以達成目標

測試7

## step 1

## 執行以下命令,對三個主節點執行cluster setslot ... node ...

## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

 

 ## 7001 上來沒有了7373 這個slot ( 7001 為7009的從節點)

 

## get gw

 

## 也確實是遷移成功了

## 問題是 從節點是否有epoch信息?

## 現在kill 掉 7009, 讓它的從節點做故障遷移

## 故障遷移成功后,集群狀態就fail 了

## 在 7001 節點上面查看slot信息

 

 ## 在7004 上面查看slot信息,也沒有了7373 的蹤影

## 主要是由於 故障遷移后 新的slave 會產生最新的epoch版本號,然后按新slave 的集群信息來更新,所以直接執行cluster setslot ... node ...是不行的。

## 這種情況應該是slave的信息中沒有7373slot的信息,所以導致這種情況

 

4.4 MIGRATE 命令測試

 測試8:

## step 1

## 腳本 設置一個 200M的值】

 

limit = 273741824
n = 100000
s = generate_random_str(n)
while sys.getsizeof(s) < limit:
n += 100000
 s += generate_random_str(n)
print(sys.getsizeof(s))

# print('success')
r = RedisCluster(host='127.0.0.1', port=7001)
r.set('lxl', s)

## step 2 

## 已經 lxl 這個鍵存在720 的槽上

## 720 在 7008 上,我把鍵移動到 7006 上

## redis-cli -p 7008 -c cluster nodes | sort -k3 | grep master

 

 

## step 3

## 源節節點執行 redis-cli -p 7006 -c cluster setslot 720 importing f96767c817d322943ba0f7b3124d5cccf79931a5

## 接收節點執行 redis-cli -p 7008 -c cluster setslot 720 migrating 99ffb47ef64d489e81d3a203a76ff099d79b6f79

## 執行完以上命令就可以在7006設置importing flag 和在7008上面設置migrating flag

 

## step 4

## 鍵gw 存在在 7008 上面

 

 ## 鍵hjw 存在 7006上面

 

 

## 開三個窗口

## 窗口1 執行:

## redis-cli -p 7008 -c MIGRATE 127.0.0.1 7006 lxl 0 100000000000 replace

## 窗口2 執行:

## redis-cli -p 7008 -c get gw

## 可以看出源節點從一開始一直阻塞

## 窗口3 執行:

## redis-cli -p 7006 -c get hjw

## 目標節點阻塞一段時間

## 因為鍵大小大概200M,可能只需要一次就能發送完,所以目標節點只在接收數據,和restore過程會阻塞,但是如果鍵值很大,需要發多次 (接收緩沖區滿了->接收數據-> restore數據 -> 緩沖區滿了或者源節點發完了-> 接收數據->restore數據,因為是redis 單線程的,所以會阻塞

 

4.5 Migrate 流程

  1. 執行的時候會阻塞進行遷移的兩個實例,直到以下任意結果發生:遷移成功,遷移失敗,等到超時
  2. 當前實例對給定 key 執行 DUMP 命令 ,將它序列化,然后傳送到目標實例,目標實例再使用 RESTORE對數據進行反序列化,並將反序列化所得的數據添加到數據庫中;當前實例就像目標實例的客戶端那樣,只要看到 RESTORE 命令返回 OK ,它就會調用 DEL 刪除自己數據庫上的 key 
  3. timeout 參數以毫秒為格式,指定當前實例和目標實例進行溝通的最大間隔時間。這說明操作並不一定要在 timeout 毫秒內完成,只是說數據傳送的時間不能超過這個 timeout 數
  4. 超時會返回IOERR,當返回IOERR時,會出現兩種情況, 1.key存在兩個實例中,2.key 只存在當前實例
  5. 如果有其他錯誤發生,那么 MIGRATE 保證 key 只會出現在當前實例中

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM