1. 程式人生 > >redis叢集實現(三)叢集刪除節點

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:7000192.168.39.153:7001192.168.39.153:7002192.168.39.153:7003192.168.39.153:7004192.168.39.153:7005。對應的node-id099cfc6fbb785449a8bf5369a53d21a9e127fa42930daea84150b5fabd32a95592781b27ceab1b71bdb62bb6ffce71588961f513c74b0d5a1a7145eaa8081e97862d9cf76c72d364f9a173187376f215

81c884ebfc919ad293f02d797aff1033025ac27e8a6707d5b9269b6260315b47f300c1ab599733b7

然後我們刪除從節點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.1537004節點就找不到了,因為他已經從每一個節點的記錄中刪除了。同事我們也看到cluster_state:ok,說明叢集狀態仍然是可用的。

那我們嘗試著刪除主節點192.168.39.1537001看看。

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.1537001後集群狀態就是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);
}

這樣,在本地伺服器看來,這個節點就被刪除了。叢集中的節點會週期性的交換資訊,一小段時間以後,整個叢集就都知道這個節點的被刪除。