redis叢集實現(三)叢集刪除節點
redis叢集裡的節點支援動態刪除,但是一般情況下不會這麼做,只有在節點軟硬體升級的時候才會主動讓節點下線。刪除節點的方式就是redis-cli客戶端連線到伺服器,然後執行cluster forget node-id就可以了,如果是刪除一個從節點的話,叢集仍然是可用狀態,如果是刪除一個主節點的話,叢集的槽位不足,就會變成不可用狀態。
下邊看下我在自己的虛擬機器執行的例子
127.0.0.1:7000> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:8 cluster_my_epoch:7 cluster_stats_messages_sent:2058 cluster_stats_messages_received:1596 127.0.0.1:7000> cluster nodes 930daea84150b5fabd32a95592781b27ceab1b71 192.168.39.153:7001 master - 0 1479044139420 2 connected 5461-10922 8a6707d5b9269b6260315b47f300c1ab599733b7 192.168.39.153:7005 slave bdb62bb6ffce71588961f513c74b0d5a1a7145ea 0 1479044141441 6 connected bdb62bb6ffce71588961f513c74b0d5a1a7145ea 192.168.39.153:7002 master - 0 1479044139925 3 connected 10923-16383 81c884ebfc919ad293f02d797aff1033025ac27e 192.168.39.153:7004 slave 930daea84150b5fabd32a95592781b27ceab1b71 0 1479044140937 2 connected 099cfc6fbb785449a8bf5369a53d21a9e127fa42 192.168.39.153:7000 myself,slave a8081e97862d9cf76c72d364f9a173187376f215 0 0 1 connected a8081e97862d9cf76c72d364f9a173187376f215 192.168.39.153:7003 master - 0 1479044140430 7 connected 0-5460
從上邊的執行結果可以看出,叢集有六個節點,分別是192.168.39.153:7000、192.168.39.153:7001、192.168.39.153:7002、192.168.39.153:7003、192.168.39.153:7004、192.168.39.153:7005。對應的node-id是099cfc6fbb785449a8bf5369a53d21a9e127fa42、930daea84150b5fabd32a95592781b27ceab1b71、bdb62bb6ffce71588961f513c74b0d5a1a7145ea、a8081e97862d9cf76c72d364f9a173187376f215、
然後我們刪除從節點192.168.39.153:7004
127.0.0.1:7000> cluster forget 81c884ebfc919ad293f02d797aff1033025ac27e OK 127.0.0.1:7000> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:5 cluster_size:3 cluster_current_epoch:8 cluster_my_epoch:7 cluster_stats_messages_sent:2403 cluster_stats_messages_received:1941
可以看到,刪除了節點後,cluster_known_nodes顯示的值就是5,如果我們輸入cluster nodes會發現原先的192.168.39.153:7004節點就找不到了,因為他已經從每一個節點的記錄中刪除了。同事我們也看到cluster_state:ok,說明叢集狀態仍然是可用的。
那我們嘗試著刪除主節點192.168.39.153:7001看看。
127.0.0.1:7000> cluster forget 930daea84150b5fabd32a95592781b27ceab1b71
OK
127.0.0.1:7000> cluster info
cluster_state:fail
cluster_slots_assigned:10922
cluster_slots_ok:10922
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:5
cluster_size:2
cluster_current_epoch:8
cluster_my_epoch:7
cluster_stats_messages_sent:2627
cluster_stats_messages_received:2165
刪除了192.168.39.153:7001後集群狀態就是cluster_state:fail,說明叢集此時是不可用的。
我們看看redis原始碼,看看forget刪除節點是怎麼實現的,在redis/cluster.c檔案裡,客戶端傳入的forget引數會進入clusterCommand函式
—————————————————————————————————
} else if (!strcasecmp(c->argv[1]->ptr,"forget") && c->argc == 3) {
// argv[2]是NODE-ID,查詢 NODE-ID 對應的節點
clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
// node-id對應的節點不在叢集中,返回錯誤
if (!n) {
addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr);
return;
//不能刪除客戶端連線到的伺服器自己,也不能刪除自己的master
} else if (n == myself) {
addReplyError(c,"I tried hard but I can't forget myself...");
return;
} else if (nodeIsSlave(myself) && myself->slaveof == n) {
addReplyError(c,"Can't forget my master!");
return;
}
// 將節點新增到黑名單
clusterBlacklistAddNode(n);
// 從叢集中刪除這個node
clusterDelNode(n);
//刪除後的下一個伺服器週期檢查會執行更新狀態,儲存當前叢集配置的操作
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|
CLUSTER_TODO_SAVE_CONFIG);
addReply(c,shared.ok);
}
—————————————————————————————————
我們繼續看clusterBlacklistAddNode函式是如何把node加入到黑名單的
// 把黑名單中的過期節點刪除,把當前node加入到黑名單裡
void clusterBlacklistAddNode(clusterNode *node) {
dictEntry *de;
sds id = sdsnewlen(node->name,REDIS_CLUSTER_NAMELEN);
// 查詢過期的節點並刪除
clusterBlacklistCleanup();
// 把node-id節點新增到黑名單裡
if (dictAdd(server.cluster->nodes_black_list,id,NULL) == DICT_OK) {
id = sdsdup(id);
}
// 設定node的過期時間
de = dictFind(server.cluster->nodes_black_list,id);
dictSetUnsignedIntegerVal(de,time(NULL)+REDIS_CLUSTER_BLACKLIST_TTL);
sdsfree(id);
}
下邊是刪除節點的關鍵函式,這個函式首先將所有由這個節點負責的槽位都標記成未分配,然後移除這個節點發送的下線報告,最後釋放本節點對這個節點的儲存,如果此節點是從節點的話,把此節點的父節點的從節點指標中刪除這個節點。
void clusterDelNode(clusterNode *delnode) {
int j;
dictIterator *di;
dictEntry *de;
//刪除所有向這個節點遷移和被遷移的槽,最後標記為未分配
for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) {
// 取消從此節點遷移槽
if (server.cluster->importing_slots_from[j] == delnode)
server.cluster->importing_slots_from[j] = NULL;
// 取消向此節點遷移槽
if (server.cluster->migrating_slots_to[j] == delnode)
server.cluster->migrating_slots_to[j] = NULL;
// 將所有這個節點負責的槽設定為未分配
if (server.cluster->slots[j] == delnode)
clusterDelSlot(j);
}
// 移除此節點發送的下線報告
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
if (node == delnode) continue;
clusterNodeDelFailureReport(node,delnode);
}
dictReleaseIterator(di);
// 將節點從它的主節點的從節點列表中移除
if (nodeIsSlave(delnode) && delnode->slaveof)
clusterNodeRemoveSlave(delnode->slaveof,delnode);
// 釋放節點
freeClusterNode(delnode);
}
這樣,在本地伺服器看來,這個節點就被刪除了。叢集中的節點會週期性的交換資訊,一小段時間以後,整個叢集就都知道這個節點的被刪除。