扩展redis cluster addslots 命令

众所周知,redis集群集群模式下必须将16384个指派完,也就是16384个槽点都在处理时,集群才处于上线上线状态。在指派槽时用的命令是这样的 “cluster addslots 1 2 3” 这样。这里有个不好的地方就是每次添加只能一个槽点一个槽点这样添加,着实不方便,我想扩展一下redis的这个命令,令他支持""cluster addslots {1…5000} "这样的语法
我们先看一下redis中 处理“ cluster addslots”命令的相关的代码,在 cluster.c的clusterCommand方法中,
具体代码如下(为了便于阅读我会添加一些中文注释以便于读者理解):

else if ((!strcasecmp(c->argv[1]->ptr,"addslots") ||
               !strcasecmp(c->argv[1]->ptr,"delslots")) && c->argc >= 3)
    {	//如果是"cluster addslots或者 cluster "delslots命令
        /* CLUSTER ADDSLOTS <slot> [slot] ... */
        /* CLUSTER DELSLOTS <slot> [slot] ... */
        int j, slot;
        unsigned char *slots = zmalloc(CLUSTER_SLOTS);	//一个数组,记录所有要添加或者删除的槽 slots[519] == 1 代表 519槽位置要删除或者添加 
        int del = !strcasecmp(c->argv[1]->ptr,"delslots"); //是否是删除命令

        memset(slots,0,CLUSTER_SLOTS);	//常规操作
        /* Check that all the arguments are parseable and that all the
         * slots are not already busy. */
         /*遍历参数列表 比如cluster addslots 1 2 3 4就是遍历 1 2 3 4 */
        for (j = 2; j < c->argc; j++) {
            if ((slot = getSlotOrReply(c,c->argv[j])) == -1) {
                zfree(slots);
                return;
            }
            if (del && server.cluster->slots[slot] == NULL) {
                addReplyErrorFormat(c,"Slot %d is already unassigned", slot);
                zfree(slots);
                return;
            } else if (!del && server.cluster->slots[slot]) {
                addReplyErrorFormat(c,"Slot %d is already busy", slot);
                zfree(slots);
                return;
            }
            if (slots[slot]++ == 1) {
                addReplyErrorFormat(c,"Slot %d specified multiple times",
                    (int)slot);
                zfree(slots);
                return;
            }
        }
        //处理所有输入 slot
        for (j = 0; j < CLUSTER_SLOTS; j++) {
            if (slots[j]) {
                int retval;

                /* If this slot was set as importing we can clear this
                 * state as now we are the real owner of the slot. */
                if (server.cluster->importing_slots_from[j])
                    server.cluster->importing_slots_from[j] = NULL;

                retval = del ? clusterDelSlot(j) :
                               clusterAddSlot(myself,j);
                serverAssertWithInfo(c,NULL,retval == C_OK);
            }
        }
        zfree(slots);
        clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
        addReply(c,shared.ok);
    }

然后改写:

else if ((!strcasecmp(c->argv[1]->ptr,"addslots") ||
               !strcasecmp(c->argv[1]->ptr,"delslots")) && c->argc >= 3)
    {
        int j;
        int del = !strcasecmp(c->argv[1]->ptr,"delslots");
        for (j = 2; j < c->argc; j++) {
           addOrDelSlots(c,c->argv[j],del);
        }

    }
void addOrDelSlots(redisClient *c,robj *o,int del){ 
    int j;   
    char *arg = o->ptr;
    // 一个数组,记录所有要添加或者删除的槽
    unsigned char *slots = zmalloc(REDIS_CLUSTER_SLOTS);
    // 将 slots 数组的所有值设置为 0
    memset(slots,0,REDIS_CLUSTER_SLOTS);
    if (strstr(arg,"{") == arg && strrchr(arg,'}') == (arg + strlen(arg) - 1))   //是否以"{"开头"}"结尾 这是区间添加参数如"{0...1000}"
    {
        char *ellipsisIndex; //省略号下标即"..."
        char startNum[6], endNum[6],startNumTemp[6],endNumTemp[6];    //because of slotNums  maximum is "16384"
        int start, end;
        memset(startNum,0,6);
        memset(endNum,0,6);
        memset(startNumTemp,0,6);
        memset(endNumTemp,0,6);

        if( (ellipsisIndex = strstr(arg,"...")) == NULL){  //客户端格式输入错误
            addReplyErrorFormat(c,"cluster addslots format error %d", ellipsisIndex);
            return;
        }
        memcpy(startNum,arg + 1,ellipsisIndex - arg - 1);  //填充startNum
        memcpy(endNum,ellipsisIndex + 3,strlen(ellipsisIndex + 3) - 1);  //填充endNum
        start = atoi(startNum);
        end = atoi(endNum);
        sprintf(startNumTemp,"%d",start);
        sprintf(endNumTemp,"%d",end);
        if (strcmp(startNum,startNumTemp) || strcmp(endNum,endNumTemp))
        {
           addReplyErrorFormat(c,"cluster addslots format error");
            return;
        }       
        if(start <= -1 || end >= REDIS_CLUSTER_SLOTS || start > end){
            addReplyErrorFormat(c,"cluster addslots out of the");
            return;
        }

        for(;start <= end; start++)
            slots[start] = 1;
    }else{
            int slot;
             // 获取 slot 数字
            if ( (slot = getSlotOrReply(c,o)) == - 1) {
                zfree(slots);
                return;
            }
            slots[slot] = 1;
    }

     // 处理所有输入 slot
        for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) {
            if (slots[j]) {
                int retval;
                if (del && server.cluster->slots[j] == NULL) {
                    addReplyErrorFormat(c,"Slot %d is already unassigned", j);
                    zfree(slots);
                    return;
                }else if (!del && server.cluster->slots[j]) {    // 如果这是 addslots 命令,并且槽已经有节点在负责,那么返回一个错误
                    addReplyErrorFormat(c,"Slot %d is already busy", j);
                    zfree(slots);
                    return;
                }

                /* If this slot was set as importing we can clear this 
                 * state as now we are the real owner of the slot. */
                // 如果指定 slot 之前的状态为载入状态,那么现在可以清除这一状态
                // 因为当前节点现在已经是 slot 的负责人了
                if (server.cluster->importing_slots_from[j])
                    server.cluster->importing_slots_from[j] = NULL;
                
                 // 添加或者删除指定 slot
                retval = del ? clusterDelSlot(j) :
                               clusterAddSlot(myself,j);
                redisAssertWithInfo(c,NULL,retval == REDIS_OK);
            }
        }
    
        zfree(slots);
        clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
        addReply(c,shared.ok);
}

这样就可以支持范围添加了像这样:cluster addslots {0…100}了。这段代码没有太大的把握保证没有bug(保不准可能各种出现段错误),我只用过一些测试案例进行测试过,貌似还行,这段代码质量和风格其实不怎么样。可能以后我会改写优化这段代码,或许吧。

之后的时间里我会写一些关于redis,JDK,和一些开源框架的文章,全都是源码级别的,从源码层面搞懂原理,从源码中学习数据结构,设计模式,算法。