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