Redis源码(十三)——集群

  这是Redis源码系列最后一篇博客咯~之前因为出去玩所以隔了很久才更新~

  在前面的博客中提到我们可以选择哨兵Sentinel+主从复制的功能来实现高可用的Redis数据库方案,从而完成复制和故障转移的需求。事实上通过Redis集群(cluster)也能起到相同的作用。集群通过分片(sharding)来进行数据共享,并提供复制和故障转移的功能。那接下来就来介绍下Redis集群的相关功能及实现。

  关于集群相关的API,在cluster.h及cluster.c文件中

一、节点

一个Redis集群由多个节点(node)组成,节点就是一个运行在集群模式下的Redis服务器。向一个节点发送CLUSTER MEET<ip><port>命令可以让节点与ip、port指定的节点进行握手,从而将指定的节点加入集群。

cluster.h/clusterNode结构保存了一个节点的状态,如创建节点的时间、节点的复制偏移量等,每个节点都用clusterNode结构记录自己的状态,并且在clusterNode结构内创建一个clusterNode数组来指向其他节点,从而记录其他节点的状态:

// 节点状态
struct clusterNode {
 
    // 创建节点的时间
   mstime_t ctime; /* Node object creation time. */
 
    // 节点的名字,由 40 个十六进制字符组成
    // 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
    charname[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
 
    // 节点标识
    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    // 以及节点目前所处的状态(比如在线或者下线)。
    intflags;      /* REDIS_NODE_... */
 
    // 节点当前的配置纪元,用于实现故障转移
   uint64_t configEpoch; /* Last configEpoch observed for this node */
 
    // 由这个节点负责处理的槽
    // 一共有 REDIS_CLUSTER_SLOTS / 8 个字节长
    // 每个字节的每个位记录了一个槽的保存状态
    // 位的值为 1 表示槽正由本节点处理,值为 0 则表示槽并非本节点处理
    // 比如 slots[0] 的第一个位保存了槽 0 的保存情况
    //slots[0] 的第二个位保存了槽 1 的保存情况,以此类推
    unsignedchar slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
 
    // 该节点负责处理的槽数量
    intnumslots;   /* Number of slots handled bythis node */
 
    // 如果本节点是主节点,那么用这个属性记录从节点的数量
    intnumslaves;  /* Number of slave nodes, ifthis is a master */
 
    // 指针数组,指向各个从节点
    structclusterNode **slaves; /* pointers to slave nodes */
 
    // 如果这是一个从节点,那么指向主节点
    structclusterNode *slaveof; /* pointer to the master node */
 
    // 最后一次发送 PING 命令的时间
   mstime_t ping_sent;      /* Unixtime we sent latest ping */
 
    // 最后一次接收 PONG 回复的时间戳
   mstime_t pong_received;  /* Unixtime we received the pong */
 
    // 最后一次被设置为 FAIL 状态的时间
   mstime_t fail_time;      /* Unixtime when FAIL flag was set */
 
    // 最后一次给某个从节点投票的时间
   mstime_t voted_time;     /* Lasttime we voted for a slave of this master */
 
    // 最后一次从这个节点接收到复制偏移量的时间
   mstime_t repl_offset_time;  /*Unix time we received offset for this node */
 
    // 这个节点的复制偏移量
    longlong repl_offset;      /* Last known reploffset for this node. */
 
    // 节点的 IP 地址
    charip[REDIS_IP_STR_LEN];  /* Latest known IPaddress of this node */
 
    // 节点的端口号
    intport;                   /* Latest knownport of this node */
 
    // 保存连接节点所需的有关信息
    clusterLink*link;          /* TCP/IP link with thisnode */
 
    // 一个链表,记录了所有其他节点对该节点的下线报告
    list*fail_reports;         /* List of nodessignaling this as failing */
 
};
typedef struct clusterNode clusterNode;

clusterNode的link属性是一个clusterLink结构,保存着连接节点所需的信息,如连接创建的时间等:

/* clusterLink encapsulates everything needed totalk with a remote node. */
// clusterLink 包含了与其他节点进行通讯所需的全部信息
typedef struct clusterLink {
 
    // 连接的创建时间
   mstime_t ctime;             /*Link creation time */
 
    // TCP 套接字描述符
    intfd;                     /* TCP socketfile descriptor */
 
    // 输出缓冲区,保存着等待发送给其他节点的消息(message)。
    sdssndbuf;                 /* Packet sendbuffer */
 
    // 输入缓冲区,保存着从其他节点接收到的消息。
    sdsrcvbuf;                 /* Packetreception buffer */
 
    // 与这个连接相关联的节点,如果没有的话就为 NULL
    structclusterNode *node;   /* Node related tothis link if any, or NULL */
 
} clusterLink;

每个节点还会保存着一个clusterState结构,此结构记录当前节点眼中集群的样子:

// 集群状态,每个节点都保存着一个这样的状态,记录了它们眼中的集群的样子。
// 另外,虽然这个结构主要用于记录集群的属性,但是为了节约资源,
// 有些与节点有关的属性,比如 slots_to_keys 、 failover_auth_count
// 也被放到了这个结构里面。
typedef struct clusterState {
 
    // 指向当前节点的指针
   clusterNode *myself;  /* This node*/
 
    // 集群当前的配置纪元,用于实现故障转移
   uint64_t currentEpoch;
 
    // 集群当前的状态:是在线还是下线
    intstate;            /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL,... */
 
    // 集群中至少处理着一个槽的节点的数量。
    intsize;             /* Num of master nodeswith at least one slot */
 
    // 集群节点名单(包括 myself 节点)
    // 字典的键为节点的名字,字典的值为 clusterNode 结构
    dict*nodes;          /* Hash table of name-> clusterNode structures */
 
    // 节点黑名单,用于 CLUSTER FORGET 命令
    // 防止被 FORGET 的命令重新被添加到集群里面
    // (不过现在似乎没有在使用的样子,已废弃?还是尚未实现?)
    dict*nodes_black_list; /* Nodes we don't re-add for a few seconds. */
 
    // 记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点
    //migrating_slots_to[i] = NULL 表示槽 i 未被迁移
    //migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A
   clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
 
    // 记录要从源节点迁移到本节点的槽,以及进行迁移的源节点
    //importing_slots_from[i] = NULL 表示槽 i 未进行导入
    //importing_slots_from[i] = clusterNode_A 表示正从节点 A 中导入槽 i
   clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
 
    // 负责处理各个槽的节点
    // 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理
   clusterNode *slots[REDIS_CLUSTER_SLOTS];
 
    // 跳跃表,表中以槽作为分值,键作为成员,对槽进行有序排序
    // 当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供方便
    // 具体操作定义在 db.c 里面
   zskiplist *slots_to_keys;
 
    /* Thefollowing fields are used to take the slave state on elections. */
    // 以下这些域被用于进行故障转移选举
 
    // 上次执行选举或者下次执行选举的时间
   mstime_t failover_auth_time; /* Time of previous or next election. */
 
    // 节点获得的投票数量
    intfailover_auth_count;    /* Number ofvotes received so far. */
 
    // 如果值为 1 ,表示本节点已经向其他节点发送了投票请求
    intfailover_auth_sent;     /* True if wealready asked for votes. */
 
    intfailover_auth_rank;     /* This slaverank for current auth request. */
 
   uint64_t failover_auth_epoch; /* Epoch of the current election. */
 
    /*Manual failover state in common. */
    /* 共用的手动故障转移状态 */
 
    // 手动故障转移执行的时间限制
   mstime_t mf_end;            /*Manual failover time limit (ms unixtime).
                                   It is zeroif there is no MF in progress. */
    /*Manual failover state of master. */
    /* 主服务器的手动故障转移状态 */
   clusterNode *mf_slave;      /*Slave performing the manual failover. */
    /*Manual failover state of slave. */
    /* 从服务器的手动故障转移状态 */
    longlong mf_master_offset; /* Master offset the slave needs to start MF
                                   or zero ifstil not received. */
    // 指示手动故障转移是否可以开始的标志值
    // 值为非 0 时表示各个主服务器可以开始投票
    intmf_can_start;           /* If non-zerosignal that the manual failover
                                   can startrequesting masters vote. */
 
    /* Thefollowign fields are uesd by masters to take state on elections. */
    /* 以下这些域由主服务器使用,用于记录选举时的状态 */
 
    // 集群最后一次进行投票的纪元
   uint64_t lastVoteEpoch;     /*Epoch of the last vote granted. */
 
    // 在进入下个事件循环之前要做的事情,以各个 flag 来记录
    inttodo_before_sleep; /* Things to do in clusterBeforeSleep(). */
 
    // 通过 cluster 连接发送的消息数量
    longlong stats_bus_messages_sent;  /* Num ofmsg sent via cluster bus. */
 
    // 通过 cluster 接收到的消息数量
    longlong stats_bus_messages_received; /* Num of msg rcvd via cluster bus.*/
 
} clusterState;

二、槽指派

Redis通过分片来保存数据库中的键值对,所谓分片是指数据库被分为了16384个槽(slot),所有的键值对都在这16384个槽中,集群中的节点会被指派处理这些槽。

如果所有的槽都有相应的节点在处理,那么这个集群处于上线状态,否则则是下线状态。通过向节点发送CLUSTER ADDSLOTS命令可以将槽指派给节点负责。

当将一个节点A加入集群时,其他节点将会为节点A创建clusterNode结构,并把这个结构加入到各自的clusterState.nodes字典中。clusterNode结构的slots及numslot属性记录了该节点负责处理的槽。slots是一个二进制位数组(bit array),长度为REDIS_CLUSTER_SLOTS/8,即16384/8字节,每一位就代表一个槽。这个保存槽指派信息的方法即是位图法,主要是为了节约内存。如果slots索引i的位被置为1,那么说明节点负责处理这个槽,否则这个槽不是由该节点处理。

一个节点不仅将自己负责的槽记录在clusterNode结构的slots数组中,并且会将slots数组发送给集群中的其他节点。clusterState结构中的slots数组记录了集群中所有槽指派的信息:clusterNode*slots[REDIS_CLUSTER_SLOTS]。slots数组的size为16384,数组每一项都指向了一个clusterNode结构,表示索引为i的槽由此节点处理。这样,我们可以通过clusterState结构的slots数组快速的知道某一个槽是由哪个节点处理的,也可以由clusterNode机构的slots数组知道,该节点处理哪些槽。


三、向集群中发命令

当集群上线后,客户端就可以向集群中的节点发送命令请求,当客户端的命令与键相关时,接受命令的节点会计算这个键所在的槽,并且检查自己是否负责这个槽,如果刚好负责这个槽则执行命令,否则节点向客户端返回MOVED错误,并转向正确的节点,并让正确的节点执行命令。请看通过键相关命令返回此键所对应的节点的逻辑:

clusterNode *getNodeByQuery(redisClient *c,struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int*error_code) {
 
    // 初始化为 NULL ,
    // 如果输入命令是无参数命令,那么 n 就会继续为 NULL
   clusterNode *n = NULL;
 
    robj*firstkey = NULL;
    intmultiple_keys = 0;
   multiState *ms, _ms;
   multiCmd mc;
    int i,slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
 
    /* Seterror code optimistically for the base case. */
    if(error_code) *error_code = REDIS_CLUSTER_REDIR_NONE;
 
    /* Wehandle all the cases as if they were EXEC commands, so we have
     * acommon code path for everything */
    // 集群可以执行事务,
    // 但必须确保事务中的所有命令都是针对某个相同的键进行的
    // 这个 if 和接下来的 for 进行的就是这一合法性检测
    if(cmd->proc == execCommand) {
        /*If REDIS_MULTI flag is not set EXEC is just going to return an
         *error. */
        if(!(c->flags & REDIS_MULTI)) return myself;
        ms= &c->mstate;
    } else{
        /*In order to have a single codepath create a fake Multi State
         *structure if the client is not in MULTI/EXEC state, this way
         *we have a single codepath below. */
        ms= &_ms;
       _ms.commands = &mc;
       _ms.count = 1;
       mc.argv = argv;
       mc.argc = argc;
       mc.cmd = cmd;
    }
 
    /*Check that all the keys are in the same hash slot, and obtain this
     * slotand the node associated. */
    for (i= 0; i < ms->count; i++) {
       struct redisCommand *mcmd;
       robj **margv;
        intmargc, *keyindex, numkeys, j;
 
       mcmd = ms->commands[i].cmd;
       margc = ms->commands[i].argc;
       margv = ms->commands[i].argv;
 
        // 定位命令的键位置
       keyindex = getKeysFromCommand(mcmd,margv,margc,&numkeys);
        // 遍历命令中的所有键
        for(j = 0; j < numkeys; j++) {
           robj *thiskey = margv[keyindex[j]];
           int thisslot = keyHashSlot((char*)thiskey->ptr,
                                      sdslen(thiskey->ptr));
 
           if (firstkey == NULL) {
               // 这是事务中第一个被处理的键
               // 获取该键的槽和负责处理该槽的节点
               /* This is the first key we see. Check what is the slot
                * and node. */
               firstkey = thiskey;
               slot = thisslot;
               n = server.cluster->slots[slot];
               redisAssertWithInfo(c,firstkey,n != NULL);
                /* If we are migrating orimporting this slot, we need to check
                * if we have all the keys in the request (the only way we
                * can safely serve the request, otherwise we return a TRYAGAIN
                * error). To do so we set the importing/migrating state and
                * increment a counter for every missing key. */
               if (n == myself &&
                   server.cluster->migrating_slots_to[slot] != NULL)
               {
                   migrating_slot = 1;
               } else if (server.cluster->importing_slots_from[slot] != NULL) {
                    importing_slot = 1;
               }
           } else {
               /* If it is not the first key, make sure it is exactly
                * the same key as the first we saw. */
               if (!equalStringObjects(firstkey,thiskey)) {
                    if (slot != thisslot) {
                        /* Error: multiple keysfrom different slots. */
                        getKeysFreeResult(keyindex);
                        if (error_code)
                            *error_code =REDIS_CLUSTER_REDIR_CROSS_SLOT;
                        return NULL;
                    } else {
                        /* Flag this request asone with multiple different
                         * keys. */
                        multiple_keys = 1;
                    }
               }
           }
 
           /* Migarting / Improrting slot? Count keys we don't have. */
           if ((migrating_slot || importing_slot) &&
               lookupKeyRead(&server.db[0],thiskey) == NULL)
           {
               missing_keys++;
           }
        }
       getKeysFreeResult(keyindex);
    }
 
    /* Nokey at all in command? then we can serve the request
     *without redirections or errors. */
    if (n== NULL) return myself;
 
    /*Return the hashslot by reference. */
    if(hashslot) *hashslot = slot;
 
    /* Thisrequest is about a slot we are migrating into another instance?
     * Thenif we have all the keys. */
 
    /* Ifwe don't have all the keys and we are migrating the slot, send
     * anASK redirection. */
    if(migrating_slot && missing_keys) {
        if(error_code) *error_code = REDIS_CLUSTER_REDIR_ASK;
        returnserver.cluster->migrating_slots_to[slot];
    }
 
    /* Ifwe are receiving the slot, and the client correctly flagged the
     *request as "ASKING", we can serve the request. However if the request
     *involves multiple keys and we don't have them all, the only option is
     * tosend a TRYAGAIN error. */
    if(importing_slot &&
       (c->flags & REDIS_ASKING || cmd->flags &REDIS_CMD_ASKING))
    {
        if(multiple_keys && missing_keys) {
           if (error_code) *error_code = REDIS_CLUSTER_REDIR_UNSTABLE;
           return NULL;
        }else {
           return myself;
        }
    }
 
    /*Handle the read-only client case reading from a slave: if this
     * nodeis a slave and the request is about an hash slot our master
     * isserving, we can reply without redirection. */
    if(c->flags & REDIS_READONLY &&
       cmd->flags & REDIS_CMD_READONLY &&
       nodeIsSlave(myself) &&
       myself->slaveof == n)
    {
       return myself;
    }
 
    /* Basecase: just return the right node. However if this node is not
     *myself, set error_code to MOVED since we need to issue a rediretion. */
    if (n!= myself && error_code) *error_code = REDIS_CLUSTER_REDIR_MOVED;
 
    // 返回负责处理槽 slot 的节点 n
    returnn;
}

需要注意的是,集群模式下,节点只能使用0号数据库。另外,节点的clusterState结构中的slots_to_keys跳跃表保存着槽与键的关系:表中以槽作为分值,键作为成员,对槽进行有序排序,当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供很大的方便。


四、重分片

集群中的槽指派并不是一成不变的,可以通过重新分片操作将指派给节点A的槽重新指派给节点B,重分片过程可以在线进行即不需要让集群下线,那么重分片是如何实现的呢?

  重分片是由Redis的集群管理软件redis-trib负责执行的,redis-trib对槽进行重分片的步骤如下:

1)对目标节点发送CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,让目标节点准备从源节点导入属于slot槽的键值对:

clusterState结构的importing_slots_from数组记录了当前节点正在从其他节点导入的槽。如果importing_slots_from[i]的指针指向一个clusterNode,就说明当前节点正在从importing_slots_from[i]指向的节点导入到槽i,当发送CLUSTER SETSLOT <slot> IMPORTING<source_id>命令后,可以将目标节点clusterState结构的importing_slots_from[i]的值设置为source_id代表的节点的clusterNode结构。

2)向源节点发送CLUSTER SETSLOT <slot> MIGRATING <target_id>命令,让源节点将属于槽slot的键值对迁移到目标节点:

clusterState结构的migrating_slots_to数组记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点,如果migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A

3)向源节点发送CLUSTER GETKEYSINSLOT <slot> <count>命令,将获得最多count个属于槽slot的键值对的键名。

4)对于步骤3中的键名,redis-trib会发送命令让被选中的键原子地从源节点迁移至目标节点。

5)重复执行步骤3、4,直至所有属于槽slot的键值对都被迁移到目标节点

6)redis-trib可以将槽slot指给向集群中任意一个节点A,并且这个信息会传播到整个集群,从而让所有的节点都知道槽slot指派给了节点A。

我们在上面说了,重分片的过程是在线的,那么这样就有一个问题,如果槽slot正在进行迁移,如果此时客户端的一个命令请求刚好是涉及到slot里的键,那么Redis是怎么处理的呢?当出现这种情况时,源节点首先会在自己的数据库中查找这个键,如果找到的话就直接执行命令,如果没找到,说明这个键已经迁移到目标节点,此时源节点将返回ASK错误,并引导客户端的命令导入正确的节点。这个操作能在函数getNodeByQuery中找到:

/* If we don't have all the keys and we aremigrating the slot, send
     * anASK redirection. */
    if(migrating_slot && missing_keys) {
        if(error_code) *error_code = REDIS_CLUSTER_REDIR_ASK;
       return server.cluster->migrating_slots_to[slot];
    }

五、集群中的复制与故障转移

在集群中也可以设置从节点对主节点进行复制“CLUSTER REPLICATE <node_id>”使用此命令可以让接收此命令的节点成为node_id的从节点。从节点复制主节点的过程与之前单机Redis使用的复制过程差不多。当某个节点成为从节点,并复制主节点这个信息会发给集群中所有的节点。集群中节点的clusterNode结构的slaves属性和numslaves属性中分别记录着主节点的从节点的信息和数目。

集群中每个节点都会定期向其他节点发送PING消息,来检测节点是否在线,如果节点没有在规定时间内返回PONG,那个发送PING的节点就会将此节点标记为疑似下线(probable fail)。clusterCron函数是Redis集群定时执行的函数,默认100ms一次,其中有关于节点疑似下线的逻辑判定:

// 计算等待 PONG 回复的时长
       delay = now - node->ping_sent;
 
        // 等待 PONG 回复的时长超过了限制值,将目标节点标记为 PFAIL (疑似下线)
        if(delay > server.cluster_node_timeout) {
           /* Timeout reached. Set the node as possibly failing if it is
            * not already in this state. */
           if (!(node->flags & (REDIS_NODE_PFAIL|REDIS_NODE_FAIL))) {
               redisLog(REDIS_DEBUG,"***NODE %.40s possibly failing",
                    node->name);
               // 打开疑似下线标记
               node->flags |= REDIS_NODE_PFAIL;
               update_state = 1;
           }
        }

集群中各个节点会通过发消息来交换集群中哥哥节点的状态,当一个主节点A知道节点B将C疑似下线时,主节点会将主节点B的下线报告(failure report)加入到clusterNode的fail_reports链表中:

int clusterNodeAddFailureReport(clusterNode*failing, clusterNode *sender) {
 
    // 指向保存下线报告的链表
    list *l= failing->fail_reports;
 
   listNode *ln;
   listIter li;
    clusterNodeFailReport*fr;
 
    /* If afailure report from the same sender already exists, just update
     * thetimestamp. */
    // 查找 sender 节点的下线报告是否已经存在
   listRewind(l,&li);
    while((ln = listNext(&li)) != NULL) {
        fr= ln->value;
        // 如果存在的话,那么只更新该报告的时间戳
        if(fr->node == sender) {
           fr->time = mstime();
           return 0;
        }
    }
 
    /*Otherwise create a new report. */
    // 否则的话,就创建一个新的报告
    fr =zmalloc(sizeof(*fr));
   fr->node = sender;
    fr->time = mstime();
 
    // 将报告添加到列表
   listAddNodeTail(l,fr);
 
    return1;
}
 
clusterProcessGossipSection函数是集群对于消息的处理函数,其中就有对疑似下线或下线节点的处理逻辑:
/* We already know this node.
              Handle failure reports, only when the sender is a master. */
            // 如果 sender 是一个主节点,那么我们需要处理下线报告
           if (sender && nodeIsMaster(sender) && node != myself) {
               // 节点处于 FAIL 或者 PFAIL 状态
               if (flags & (REDIS_NODE_FAIL|REDIS_NODE_PFAIL)) {
 
                    // 添加 sender 对 node 的下线报告
                    if(clusterNodeAddFailureReport(node,sender)) {
                        redisLog(REDIS_VERBOSE,
                            "Node %.40sreported node %.40s as not reachable.",
                            sender->name,node->name);
                    }
 
                    // 尝试将 node 标记为 FAIL
                   markNodeAsFailingIfNeeded(node);
 
               // 节点处于正常状态
               } else {
 
                    // 如果 sender 曾经发送过对 node 的下线报告
                    // 那么清除该报告
                    if(clusterNodeDelFailureReport(node,sender)) {
                        redisLog(REDIS_VERBOSE,
                            "Node %.40sreported node %.40s is back online.",
                            sender->name,node->name);
                    }
               }
           }

当节点被标记为疑似下线PFAIL时,会判断这个节点是否真的下线了。判断逻辑是向集群中其他节点询问是否认为此节点下线,如果半数以上节点认为此节点下线了,那么这个节点就会被标记为下线FAIL:

void markNodeAsFailingIfNeeded(clusterNode *node){
    intfailures;
 
    // 标记为 FAIL 所需的节点数量,需要超过集群节点数量的一半
    intneeded_quorum = (server.cluster->size / 2) + 1;
 
    if(!nodeTimedOut(node)) return; /* We can reach it. */
    if(nodeFailed(node)) return; /* Already FAILing. */
 
    // 统计将 node 标记为 PFAIL 或者 FAIL 的节点数量(不包括当前节点)
   failures = clusterNodeFailureReportsCount(node);
 
    /* Alsocount myself as a voter if I'm a master. */
    // 如果当前节点是主节点,那么将当前节点也算在 failures 之内
    if(nodeIsMaster(myself)) failures++;
    // 报告下线节点的数量不足节点总数的一半,不能将节点判断为 FAIL ,返回
    if(failures < needed_quorum) return; /* No weak agreement from masters. */
 
   redisLog(REDIS_NOTICE,
       "Marking node %.40s as failing (quorum reached).",node->name);
 
    /* Markthe node as failing. */
    // 将 node 标记为 FAIL
   node->flags &= ~REDIS_NODE_PFAIL;
   node->flags |= REDIS_NODE_FAIL;
   node->fail_time = mstime();
 
    /*Broadcast the failing node name to everybody, forcing all the other
     *reachable nodes to flag the node as FAIL. */
    // 如果当前节点是主节点的话,那么向其他节点发送报告 node 的 FAIL 信息
    // 让其他节点也将 node 标记为 FAIL
    if(nodeIsMaster(myself)) clusterSendFail(node->name);
   clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
}

当从节点发现主节点下线时,就会对主节点进行故障转移,步骤总体与哨兵中的逻辑类似:

1)从主节点的所有从节点中选择一个节点成为主节点

2)新的主节点会将已下线的主节点的槽指派全部取消,并把这些槽全部指派给自己

3)新主节点会向集群广播一条PONG消息,从而让其他主节点知道这个节点成为了新的主节点

4)新主节点开始接收处理和自己负责的槽相关的命令请求,故障转移完成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值