Redis 集群

 

 

集群的概念早在 Redis 3.0 以前讨论了,3.0 才在源码中出现。Redis 集群要考虑的问题:node

  1. 节点之间怎么据的同步,如何作到数据一致性。一主一备的模式,能够用 Redis 内部实现的主从备份实现数据同步。但节点不断增多,存在多个 master 的时候,同步的难度会越大。
  2. 如何作到负载均衡?请求量大的时候,如何将请求尽可能均分到各个服务器节点,负载均衡算法作的很差会致使雪崩。
  3. 如何作到平滑拓展?当业务量增长的时候,可否经过简单的配置即让新的 Redis 节点变为可用。
  4. 可用性如何?当某些节点鼓掌,可否快速恢复服务器集群的工做能力。
  5. ……

一个稳健的后台系统须要太多的考虑。redis

一致性哈希算法(consistent hashing)

背景

一般,业务量较大的时候,考虑到性能的问题(索引速度慢和访问量过大),不会把全部的数据存放在一个 Redis 服务器上。这里须要将一堆的键值均分存储到多个 Redis 服务器,能够经过:算法

target = hash(key)\%N,其中 target 为目标节点,key 为键,N 为 Redis 节点的个数哈希取余的方式会将不一样的 key 分发到不一样的服务器上。数据库

但考虑以下场景:后端

  1. 业务量忽然增长,现有服务器不够用。增长服务器节点后,依然经过上面的计算方式:hash(key)%(N+1) 作数据分片和分发,但以前的 key 会被分发到与以前不一样的服务器上,致使大量的数据失效,须要从新写入(set)Redis 服务器。
  2. 其中的一个服务器挂了。若是不作及时的修复,大量被分发到此服务器请求都会失效。

这也是两个问题。数组

一致性哈希算法

设定一个圆环上 0 23̂2-1 的点,每一个点对应一个缓存区,每一个键值对存储的位置也经哈希计算后对应到环上节点。但现实中不可能有如此多的节点,因此假若键值对经哈希计算后对应的位置没有节点,那么顺时针找一个节点存储它。缓存

考虑增长服务器节点的状况,该节点顺时针方向的数据仍然被存储到顺时针方向的节点上,但它逆时针方向的数据被存储到它本身。这时候只有部分数据会失效,被映射到新的缓存区。服务器

考虑节点减小的状况。该缺失节点顺时针方向上的数据仍然被存储到其顺时针方向上的节点,设为 beta,其逆时针方向上的数据会被存储到 beta 上。一样,只有有部分数据失效,被从新映射到新的服务器节点。网络

这种状况比较麻烦,上面图中 gamma 节点失效后,会有大量数据映射到 alpha 节点,最怕 alpha 扛不住,接下去 beta 也扛不住,这就是多米诺骨牌效应;)。这里涉及到数据平衡性和负载均衡的话题。数据平衡性是说,数据尽量均分到每一个节点上去,存储达到均衡。数据结构

虚拟节点简介

将多个虚拟节点对应到一个真实的节点,存储能够达到更均衡的效果。以前的映射方案为:

key -> node

中间多了一个层虚拟节点后,多了一层映射关系:

key -> <virtual node> -> node

为何须要虚拟节点

虚拟节点的设计有什么好处?假设有四个节点以下:

节点 3 忽然宕机,这时候本来在节点 3 的数据,会被定向到节点 4。在三个节点中节点 4 的请求量是最大的。这就致使节点与节点之间请求量是不均衡的。

为了达到节点与节点之间请求访问的均衡,尝试将原有节点 3 的数据平均定向到到节点 1,2,4. 如此达到负载均衡的效果,以下:

总之,一致性哈希算法是但愿在增删节点的时候,让尽量多的缓存数据不失效。

怎么实现?

一致性哈希算法,既能够在客户端实现,也能够在中间件上实现(如 proxy)。在客户端实现中,当客户端初始化的时候,须要初始化一张预备的 Redis 节点的映射表:hash(key)=> . 这有一个缺点,假设有多个客户端,当映射表发生变化的时候,多个客户端须要同时拉取新的映射表。

另外一个种是中间件(proxy)的实现方法,即在客户端和 Redis 节点之间加多一个代理,代理通过哈希计算后将对应某个 key 的请求分发到对应的节点,一致性哈希算法就在中间件里面实现。能够发现,twemproxy 就是这么作的。

twemproxy - Redis 集群管理方案

twemproxy 是 twitter 开源的一个轻量级的后端代理,兼容 redis/memcache 协议,可用以管理 redis/memcache 集群。

twemproxy 内部有实现一致性哈希算法,对于客户端而言,twemproxy 至关因而缓存数据库的入口,它无需知道后端的部署是怎样的。twemproxy 会检测与每一个节点的链接是否健康,出现异常的节点会被剔除;待一段时间后,twemproxy 会再次尝试链接被剔除的节点。

一般,一个 Redis 节点池能够分由多个 twemproxy 管理,少数 twemproxy 负责写,多数负责读。twemproxy 能够实时获取节点池内的全部 Redis 节点的状态,但其对故障修复的支持还有待提升。解决的方法是能够借助 redis sentinel 来实现自动的主从切换,当主机 down 掉后,sentinel 会自动将从机配置为主机。而 twemproxy 能够定时向 redis sentinel 拉取信息,从而替换出现异常的节点。

twemproxy 的更多细节,这里再也不作深刻的讨论。

 

Redis 官方版本支持的集群

最新版本的 Redis 也开始支持集群特性了,不再用靠着外援过日子了。基本的思想是,集群里的每一个 Redis 都只存储必定的键值对,这个“必定”能够经过默认或自定义的哈希函数来决定,当一个 Redis 收到请求后,会首先查看此键值对是否该由本身来处理,是则继续往下执行;不然会产生一个相似于 http 3XX 的重定向,要求客户端去请求集群中的另外一个 Redis。

Redis 每个实例都会经过遵照必定的协议来维护这个集群的可用性,稳定性。有兴趣可前往官网了解 Redis 集群的实现细则。

 

redis cluster 就是想要让一群的节点实现自治,有自我修复的功能,数据分片和负载均衡。

数据结构

基本上集群中的每个节点都须要知道其余节点的状况,从而,若是网络中有五个节点就下面的图:

其中每条线都表明双向联通。特别的,若是 redis master 还配备了 replica,图画起来会稍微复杂一点。

redis cluster 中有几个比较重要的数据结构,一个用以描述节点 struct clusterNode,一个用以描述集群的情况 struct clusterState。

节点的信息包括:自己的一些属性,还有它的主从节点,心跳和主从复制信息,和与该节点的链接上下文。

typedef struct clusterNode {
    mstime_t ctime; /* Node object creation time. */
    char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
    int flags; /* REDIS_NODE_... */
    uint64_t configEpoch; /* Last configEpoch observed for this node */
    // 该节点会处理的slot
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
    int numslots; /* Number of slots handled by this node */
    // 从机信息
    int numslaves; /* Number of slave nodes, if this is a master */
    // 从机节点数组
    struct clusterNode **slaves; /* pointers to slave nodes */
    // 主机节点数组
    struct clusterNode *slaveof; /* pointer to the master node */
    // 一些有用的时间点
    mstime_t ping_sent; /* Unix time we sent latest ping */
    mstime_t pong_received; /* Unix time we received the pong */
    mstime_t fail_time; /* Unix time when FAIL flag was set */
    mstime_t voted_time; /* Last time we voted for a slave of this master */
    mstime_t repl_offset_time; /* Unix time we received offset for this node */
    long long repl_offset; /* Last known repl offset for this node. */
    // 最近被记录的地址和端口
    char ip[REDIS_IP_STR_LEN]; /* Latest known IP address of this node */
    int port; /* Latest known port of this node */
    // 与该节点的链接上下文
    clusterLink *link; /* TCP/IP link with this node */
    list *fail_reports; /* List of nodes signaling this as failing */
    } clusterNode;
    集群的状态包括下面的信息:
typedef struct clusterState {
    clusterNode *myself; /* This node */
    // 配置版本
    uint64_t currentEpoch;
    // 集群的状态
    int state; /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */
    // 存储全部节点的哈希表
    int size; /* Num of master nodes with at least one slot */
    dict *nodes; /* Hash table of name -> clusterNode structures */
    // 黑名单节点,一段时间内不会再加入到集群中
    dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
    // slot 数据正在迁移到migrating_slots_to[slot] 节点
    clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
    // slot 数据正在从importing_slots_from[slot] 迁移到本机
    clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
    // slot 数据由slots[slot] 节点来处理
    clusterNode *slots[REDIS_CLUSTER_SLOTS];
    // slot 到key 的一个映射
    zskiplist *slots_to_keys;
    // 记录了故障修复的信息
    /* The following fields are used to take the slave state on elections. */
    mstime_t failover_auth_time; /* Time of previous or next election. */
    int failover_auth_count; /* Number of votes received so far. */
    int failover_auth_sent; /* True if we already asked for votes. */
    int failover_auth_rank; /* This slave rank for current auth request. */
    uint64_t failover_auth_epoch; /* Epoch of the current election. */
    int cant_failover_reason; /* Why a slave is currently not able to
    failover. See the CANT_FAILOVER_* macros. */
    // 人工故障修复的一些信息
    ......
} clusterState;

 

在正常的 Redis 集群中的任何一个节点都能感知到其余节点。

上面频繁出现 slot 单词。以前咱们学哈希表的时候,能够把 slot 理解为哈希表中的桶(bucket)。为何须要slot?这和 redis cluster 的数据分区和访问有关。建议大概看完 Redis 对数据结构后,接着看 clusterCommand() 这个函数,由此知道 redis cluster 能提供哪些服务和功能。接着往下看。

数据访问

在 http 有 301 状态码:301 Moved Permanently,它表示用户所要访问的内容已经迁移到一个地址了,须要向新的地址发出请求。redis cluster 很明显也是这么作的。在前面讲到,redis cluster 中的每个节点都须要知道其余节点的状况,这里就包括其余节点负责处理哪些键值对。

在主函数中,Redis 会检测在启用集群模式的状况下,会检测命令中指定的 key 是否该由本身来处理,若是不是的话,会返回一个相似于重定向的错误返回到客户端。而“是否由本身来处理”就是看 hash(key) 值是否落在本身所负责的 slot 中。

 

typedef struct clusterNode {
    ......
    // 该节点会处理的slot
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
    int numslots; /* Number of slots handled by this node */
    ......
} clusterNode;

可能会有疑问:这样的数据访问机制在不是会浪费一个请求吗?确实,若是直接向集群中的节点盲目访问一个 key 的话,确实须要发起两个请求。为此,redis cluster 配备了 slot 表,用户经过 slots 命令先向集群请求这个 slot 表,获得这个表能够获取哪些节点负责哪些 slot,继而客户端能够访问再访问集群中的数据。这样,就能够在大多数的场景下节省一个请求,直达目标节点。固然,这个 slot 表是随时出现变动的,因此客户端不可以一 本万利一直使用这个 slot 表,能够实现一个定时器,超时后再向集群节点获取 slot 表。

你能够阅读 getNodeByQuery(),流程不难。

新的节点

Redis 刚刚启动时候会检测集群配置文件中是否有预配置好的节点,若是有的话,会添加到节点哈希表中,在适当的时候链接这个节点,并和它打招呼–握手。

// 加载集群配置文件
int clusterLoadConfig(char *filename) {
    ......
    // 若是该节点不在哈希表中,会添加
    /* Create this node if it does not exist */
    n = clusterLookupNode(argv[0]);
    if (!n) {
        n = createClusterNode(argv[0],0);
        clusterAddNode(n);
    }
    /* Address and port */
    if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
    *p = '\0';
    memcpy(n->ip,argv[1],strlen(argv[1])+1);
    n->port = atoi(p+1);
    ......
}

注意,加载配置文件后,不会当即进入握手阶段。 另外两个新增节点的时机,当其余节点向该节点打招呼时候,该节点会记录下对端节点,以及对端所知悉的节点;Redis 管理人员告知,Redis 管理人员能够经过普通的 redis meet 命令,至关因而人工将某个节点加入到集群中。

 

当和其余节点开始握手时,会调用 clusterStartHandshake(),它只会初始化握手的初始信息,并不会马上向其余节点发起握手,:按照 Redis 的习惯是在集群定时处理函数 clusterCron() 中。

int clusterStartHandshake(char *ip, int port) {
    clusterNode *n;
    char norm_ip[REDIS_IP_STR_LEN];
    struct sockaddr_storage sa;
    // 分IPV4 和IPV6 两种状况
    /* IP sanity check */
    if (inet_pton(AF_INET,ip,
        &(((struct sockaddr_in *)&sa)->sin_addr)))
    {
        sa.ss_family = AF_INET;
    } else if (inet_pton(AF_INET6,ip,
        &(((struct sockaddr_in6 *)&sa)->sin6_addr)))
    {
        sa.ss_family = AF_INET6;
    } else {
        errno = EINVAL;
        return 0;
    }
    // 端口有效性检测
    /* Port sanity check */
    if (port <= 0 || port > (65535-REDIS_CLUSTER_PORT_INCR)) {
        errno = EINVAL;
        return 0;
    }
    // 标准化ip
    /* Set norm_ip as the normalized string representation of the node
    * IP address. */
    memset(norm_ip,0,REDIS_IP_STR_LEN);
    if (sa.ss_family == AF_INET)
        inet_ntop(AF_INET,
            (void*)&(((struct sockaddr_in *)&sa)->sin_addr),
            norm_ip,REDIS_IP_STR_LEN);
    else
        inet_ntop(AF_INET6,
            (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr),
            norm_ip,REDIS_IP_STR_LEN);
    // 若是这个节点正在握手状态,则不须要重复进入,直接退出
    if (clusterHandshakeInProgress(norm_ip,port)) {
        errno = EAGAIN;
        return 0;
    }
    // 建立一个新的节点,并加入到节点哈希表中
    /* Add the node with a random address (NULL as first argument to
    * createClusterNode()). Everything will be fixed during the
    * handshake. */
    n = createClusterNode(NULL,REDIS_NODE_HANDSHAKE|REDIS_NODE_MEET);
    memcpy(n->ip,norm_ip,sizeof(n->ip));
    n->port = port;
    clusterAddNode(n);
    return 1;
}

其余节点收到后在 clusterProcessGossipSection() 中将新的节点添加到哈希表中。

心跳机制

仍是那句话,redis cluster 中的每个节点都须要知道其余节点的状况。要达到这个目标,必须有一个心跳机制来保持每一个节点是可达的,监控的,而且节点的信息变动,也能够经过心跳中的数据包来传递。这样就很容易理解Redis 的心跳机制是怎么实现的。这有点相似于主从复制中的实现方法,总之就是一个心跳。在 redis cluster 只,这种心跳又叫 gossip 机制。

集群之间交互信息是用其内部专用链接的。redis cluster 中的每个节点都监听了一个集群专用的端口,专门用做集群节点之间的信息交换。在 redis cluster 初始化函数 clusterInit() 中监听了该端口,并在事件中心注册了 clusterAcceptHandler()。 从 clusterAcceptHandler()的逻辑来看,当有新的链接到来时,会为新的链接注册 clusterReadHandler()回调函数。这一点和 redis 自己初始化的行为是一致的。

clusterSendPing() 中发送心跳数据。发送的包括:所知节点的名称,ip 地址等。这样,改节点就将主机所知的信息传播到了其余的节点。注意,从 clusterSendPing() 的实现来看,redis cluster 并非一开始就向全部的节点发送心跳数据,而选取几个节点发送,由于 redis 考虑到集群网的造成并不须要每一个节点向像集群中的全部其余节点发送 ping。

故障修复

故障修复曾经在主从复制中提到过。redis cluster 的故障修复分两种途径,一种是集群自治实现的故障修复,一种是人工触发的故障修复。

集群自治实现的故障修复中,是由从机发起的。上面所说,集群中的每一个节点都须要和其余节点保持链接。从机若是检测到主机节点出错了(标记为 REDIS_NODE_FAIL),会尝试进行主从切换。在 cluster 定时处理函数中,有一段只有从机才会执行的代码段:

// 集群定时处理函数
/* This is executed 10 times every second */
void clusterCron(void) {
    ......
    // 从机才须要执行下面的逻辑
    if (nodeIsSlave(myself)) {
        ......
        // 从机-> 主机替换
        clusterHandleSlaveFailover();
        ......
    }
    ......
}

从机的 clusterCron() 会调用 clusterHandleSlaveFailover() 已决定是否须要执行故障修复。一般,故障修复的触发点就是在其主机被标记为出错节点的时候。

故障修复的协议

在决定故障修复后,会开始进行协商是否能够将本身升级为主机。

// 主机已是一个出错节点了,本身做为从机能够升级为主机
void clusterHandleSlaveFailover(void) {
    ......
    // 故障修复超时,从新启动故障修复
    if (auth_age > auth_retry_time) { // 两次故障修复间隔不能太短
        // 更新一些时间
        ......
        redisLog(REDIS_WARNING,
        "Start of election delayed for %lld milliseconds "
        "(rank #%d, offset %lld).",
        server.cluster->failover_auth_time - mstime(),
        server.cluster->failover_auth_rank,
        replicationGetSlaveOffset());
        // 告知其余从机
        /* Now that we have a scheduled election, broadcast our offset
        * to all the other slaves so that they'll updated their offsets
        * if our offset is better. */
        clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES);
        return;
    }
    ......
    // 开头投票
    /* Ask for votes if needed. */
    if (server.cluster->failover_auth_sent == 0) {
        server.cluster->currentEpoch++;
        server.cluster->failover_auth_epoch = server.cluster->currentEpoch;
        redisLog(REDIS_WARNING,"Starting a failover election for epoch %llu.",
        (unsigned long long) server.cluster->currentEpoch);
        clusterRequestFailoverAuth();
        server.cluster->failover_auth_sent = 1;
        clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
        CLUSTER_TODO_UPDATE_STATE|
        CLUSTER_TODO_FSYNC_CONFIG);
        return; /* Wait for replies. */
    }
}

上面有两个关键的函数:clusterBroadcastPong() 和clusterRequestFailoverAuth()。

在 clusterBroadcastPong()中,会向其余属于同一主从关系的其余从机发送 pong,以传递主机已经出错的信息。

clusterRequestFailoverAuth() 中,会向集群中的全部其余节点发送 CLUSTERMSG_-TYPE_FAILOVER_AUTH_REQUEST 信令,意即询问是否投票。

那收到这个信令的节点,是否会向源节点投票呢?先来看看 FAILOVERAUTH-REQUEST 信令中带有什么数据,顺着 clusterRequestFailoverAuth() 下去,会找到 cluster-BuildMessageHdr() 函数,它打包了一些数据。这里主要包括:

  1. RCmb 四个字符,至关因而 redis cluster 信令的头部校验值,
  2. type,命令号,这是属于什么信令
  3. sender info,发送信令节点的信息
  4. 发送信令节点的配置版本号
  5. 主从复制偏移量

在心跳机制那一节讲过,集群节点会为与其余节点的链接注册clusterReadHandler() 回调函数,FAILOVER_AUTH_REQUEST 信令的处理也在里面,对应的是 clusterSendFailover-AuthIfNeeded() 处理函数,在这里决定是否投对端节点一票。这里的决定因素有几个:配置版本号,节点自己和投票时间。

1.若是须要投票,索取投票的节点当前版本号必须比当前记录的版本同样,这样才有权索取投票;新的版本号必须是最新的。第二点,可能比较绕,譬以下面的场景,slave 是没法得到其余主机的投票的,other slave 才能够。这里的意思是,若是一个从机想要升级为主机,它与它的主机必须保持状态一致。

2.索取投票的节点必须是从机节点。这是固然,由于故障修复是由从机发起的

3.最后一个是投票的时间,由于当一个主机有多个从机的时候,多个从机都会发起故障修复,一段时间内只有一个从机会进行故障修复,其余的会被推迟。

这三点都在 clusterSendFailoverAuthIfNeeded() 中都有所体现。

当都知足了上述要求事后,便可开始投票:

// 决定是否投票,redis cluster 将根据配置的版本号决定是否投票
/* Vote for the node asking for our vote if there are the conditions. */
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
    ......
    // 投票走起
    /* We can vote for this slave. */
    clusterSendFailoverAuth(node);
    server.cluster->lastVoteEpoch = server.cluster->currentEpoch;
    node->slaveof->voted_time = mstime(); // 更新投票的时间
    redisLog(REDIS_WARNING, "Failover auth granted to %.40s for epoch %llu",
    node->name, (unsigned long long) server.cluster->currentEpoch);
}

投票函数 lusterSendFailoverAuth() 只是放一个 CLUSTERMSG_TYPEFAILOVER-AUTH_ACK 信令到达索取投票的从机节点,从而该从机获取了一票。让咱们再回到索取投票的从机节点接下来会怎么作。

// 主机已是一个出错节点了,本身做为从机能够升级为主机
void clusterHandleSlaveFailover(void) {
    ......
    // 得到的选票必须是集群节点数的通常以上
    /* Check if we reached the quorum. */
    if (server.cluster->failover_auth_count >= needed_quorum) {
        /* We have the quorum, we can finally failover the master. */
        redisLog(REDIS_WARNING,
        "Failover election won: I'm the new master.");
        /* Update my configEpoch to the epoch of the election. */
    if (myself->configEpoch < server.cluster->failover_auth_epoch) {
        myself->configEpoch = server.cluster->failover_auth_epoch;
        redisLog(REDIS_WARNING,
        "configEpoch set to %llu after successful failover",
        (unsigned long long) myself->configEpoch);
    }
    // 正式转换为主机,代替主机的功能
    /* Take responsability for the cluster slots. */
    clusterFailoverReplaceYourMaster();
    } else {
        clusterLogCantFailover(REDIS_CLUSTER_CANT_FAILOVER_WAITING_VOTES);
    }
}

从机在获取集群节点数量半数以上的投票时,就能够正式升级为主机了。来回顾一下投票的全过程:

clusterFailoverReplaceYourMaster() 就是将其自身的配置从从机更新到主机,最后广播给全部的节点:我转正了。实际上,是发送一个 pong。

redis cluster 还提供了一种人工故障修复的模式,管理人员能够按需使用这些功能。你能够从 clusterCommand() 下找到人工故障修复流程开始执行的地方:

1.cluster failover takeover. 会强制将从机升级为主机,不须要一个投票的过程。

2.cluster failover force. 会强制启用故障修复,这和上面讲的故障修复过程同样。若是你留意 clusterHandleSlaveFailover() 中的处理逻辑的话,实际 cluster force 也是在其中处理的,一样须要一个投票的过程。

3.cluster failover. 默认的模式,会先告知主机须要开始进行故障修复流程,主机被告知会中止服务。以后再走接下来的主从修复的流程。

// cluster 命令处理。
void clusterCommand(redisClient *c) {
    ......
    // 启动故障修复
    } else if (!strcasecmp(c->argv[1]->ptr,"failover") &&
        (c->argc == 2 || c->argc == 3))
    {
    ......
    if (takeover) {
        ......
        // 产生一个新的配置版本号
        clusterBumpConfigEpochWithoutConsensus();
        // 直接将本身升级为主机,接着通知到全部的节点
        clusterFailoverReplaceYourMaster();
    } else if (force) {
        ......
        // 直接标记为能够开始进行故障修复了,并不用告知主机
        server.cluster->mf_can_start = 1;
    } else {
        // 先通知个人主机开始人工故障修复,再执行接下来的故障修复流程
        clusterSendMFStart(myself->slaveof);
        }
        addReply(c,shared.ok);
    }
    ......
}

4.发送信令节点的配置版本号

5.主从复制偏移量

人工故障修复模式,和自治实现的故障修复模式最大的区别在于对于从机来讲,其主机是否可达。人工故障修复模式,容许主机可达的状况下,实现故障修复。所以,相比自治的故障修复,人工的还会多一道工序:主从复制的偏移量相等事后,才开始进行故障修复的过程。

从下面两种模式的处理来看,有很明显的区别:

// cluster 命令处理
void clusterCommand(redisClient *c) {
    ......
    // 启动故障修复
    } else if (!strcasecmp(c->argv[1]->ptr,"failover") &&
        (c->argc == 2 || c->argc == 3))
    {
        ......
    if (takeover) {
        ......
        // 产生一个新的配置版本号
        clusterBumpConfigEpochWithoutConsensus();
        // 直接将本身升级为主机,接着通知到全部的节点
        clusterFailoverReplaceYourMaster();
    } else if (force) {
        ......
        // 直接标记为能够开始进行故障修复了,并不用告知主机
        server.cluster->mf_can_start = 1;
    } else {
        // 先通知个人主机开始人工故障修复,再执行接下来的故障修复流程
        clusterSendMFStart(myself->slaveof);
        }
    addReply(c,shared.ok);
    }
}
  1. takeover 模式直接将本身升级为主机
  2. force 模式直接进入故障修复模式
  3. 默认模式会先告知(clusterSendMFStart())主机,接着再进行故障修复流程

来看看人工故障修复模式的状态机 clusterHandleManualFailover(),这个函数只会在 clusterCron() 中调用:

// 人工恢复状态机, 只在clusterCron() 中调用
void clusterHandleManualFailover(void) {
    /* Return ASAP if no manual failover is in progress. */
    if (server.cluster->mf_end == 0) return;
        /* If mf_can_start is non-zero, the failover was already triggered so the
        * next steps are performed by clusterHandleSlaveFailover(). */
    if (server.cluster->mf_can_start) return;
    if (server.cluster->mf_master_offset == 0) return; /* Wait for offset... */
    if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) {
        /* Our replication offset matches the master replication offset
        * announced after clients were paused. We can start the failover. */
        server.cluster->mf_can_start = 1;
        redisLog(REDIS_WARNING,
        "All master replication stream processed, "
        "manual failover can start.");
    }
}

主要的几个变量这里解说一下:

  1. mf_end:在触发人工故障修复的时候就会被设置
  2. mf_master_offset:从机须要等待主机发送主从复制偏移量,如上所说,从机升级为 主机,须要和主机的偏移量相等
  3. mf_can_start:主从机偏移量相等时,就能够进行故障修复了

自治故障修复和人工故障修复流程都是在 clusterHandleSlaveFailover() 中开始执行的。这里再也不复述。

这里大概总结一下人工故障修复默认模式的流程:

数据迁移

在以前有讲过 migrate 系列的命令,即数据迁移。在 redis cluster 中,搬迁 slot 的时候,就会用到 migrate 系列的命令。

为了管理链接,redis cluster 还实现了长链接的管理,你能够在 migrateGetSocket() 中查看它的实现。

在集群状态结构体中存储了两个与数据迁移的数据:

typedef struct clusterState {
    ......
    // slot 数据正在迁移到migrating_slots_to[slot] 节点
    clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
    // slot 数据正在从importing_slots_from[slot] 迁移到本机
    clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
    ......
} clusterState;

这些信息在数据访问的时候会有用。

相关文章
相关标签/搜索