疯狂的小鸡

Redis集群-Cluster实现原理

字数统计: 3k阅读时长: 10 min
2018/10/07 Share

更多Redis入门系列文章,参见Redis入门-大纲

心跳机制

Cluster中的每个节点都维护一份在自己看来当前整个集群的状态,主要包括:

  • 当前集群状态

  • 集群中各节点所负责的slots信息,及其migrate状态

  • 集群中各节点的master-slave状态

  • 集群中各节点的存活状态及不可达投票

当集群状态变化时,如新节点加入、slot迁移、节点宕机、slave提升为新Master,我们希望这些变化尽快的被发现,传播到整个集群的所有节点并达成一致。节点之间相互的心跳(PING,PONG,MEET)及其携带的数据是集群状态传播最主要的途径。

心跳时机

Redis节点会记录其向每一个节点上一次发出ping和收到pong的时间,心跳发送时机与这两个值有关。

通过下面的方式既能保证及时更新集群状态,又不至于使心跳数过多:

  • 每次Cron向所有未建立链接的节点发送ping或meet

  • 每1秒从所有已知节点中随机选取5个,向其中上次收到pong最久远的一个发送ping

  • 每次Cron向收到pong超过timeout/2的节点发送ping

  • 收到ping或meet,立即回复pong

心跳数据

Header,发送者自己的信息

  • 所负责slots的信息
  • 主从信息
  • ip port信息
  • 状态信息

Gossip,发送者所了解的部分其他节点的信息

  • ping_sent, pong_received
  • ip, port信息
  • 状态信息,比如发送者认为该节点已经不可达,会在状态信息中标记其为PFAIL或FAIL

心跳处理

新节点加入

向已知结点发送meet包加入集群。从收到的回复pong包中的gossip得到未知的其他节点。循环上述过程,直到最终加入集群。

Slots信息

判断发送者声明的slots信息,跟本地记录的是否有不同。如果不同,且发送者epoch较大,更新本地记录。如果不同,且发送者epoch小,发送Update信息通知发送者。

Master slave信息

发现发送者的master、slave信息变化,更新本地状态。

节点Fail探测

详见宕机处理

广播

当需要发布一些非常重要需要立即送达的信息时,上述心跳加Gossip的方式就显得捉襟见肘了,这时就需要向所有集群内机器的广播信息,使用广播发的场景:

  • 节点的Fail信息:当发现某一节点不可达时,探测节点会将其标记为PFAIL状态,并通过心跳传播出去。当某一节点发现这个节点的PFAIL超过半数时修改其为FAIL并发起广播。

  • Failover Request信息:slave尝试发起FailOver时广播其要求投票的信息。

  • 新Master信息:Failover成功的节点向整个集群广播自己的信息。

宕机处理

节点失效检测

1)当一个节点向另一个节点发送 PING 命令,但是目标节点未能在给定的时限内返回 PONG 命令时,那么发送命令的节点会将目标节点标记为PFAIL (possible failure,可能已失效)。

2)等待 PING 命令回复的时限称为“节点超时时限(node timeout)”,是一个节点选项(node-wise setting)。

3)当节点接收到其他节点发来的ping时,它会读取gossip包,记下那些被其他节点标记为失效的节点。这称为失效报告(failure report)。失效报告有过期时限,过期的失效报告会被移除,所以主节点要将某个节点标记为 FAIL 的话,必须以最近接收到的失效报告作为根据。

4)如果节点已经将某个节点标记为 PFAIL ,并且根据节点所收到的失效报告显式,集群中的大部分其他主节点也认为那个节点进入了失效状态,那么节点会将那个失效节点的状态标记为 FAIL 。

Gossip的存在使得集群状态的改变可以更快的达到整个集群。每个心跳包中会包含多个Gossip包,那么多少个才是合适的呢,redis的选择是N/10,其中N是节点数,这样可以保证在PFAIL投票的过期时间内,节点可以收到80%机器关于失败节点的gossip,从而使其顺利进入FAIL状态。

5)一旦某个节点被标记为 FAIL ,关于这个节点已失效的信息就会被广播到整个集群,所有接收到这条信息的节点都会将失效节点标记为 FAIL 。

节点宕机处理

一旦某个从节点进入FAIL状态,对该节点的直接访问将被拒绝Connection Refused。

一旦某个主节点进入 FAIL 状态,如果这个主节点有一个或多个从节点存在,则会进入从节点选举。

集群FAIL判断

  • 某个主节点和所有从节点全部挂掉,我们集群就进入faill状态。

  • 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

  • 如果集群任意master挂掉,且当前master没有slave.集群进入fail状态。

从节点选举

集群currentEpoch

Redis Cluster使用了类似于Raft算法“term”(任期)的概念,那么在redis Cluster中term称为epoch,用来给events增量版本号。当多个nodes提供了信息有冲突时,它可以作为node来知道哪个状态是最新的。currentEpoch为一个64位无签名数字。

在集群node创建时,master和slave都会将各自的currentEpoch设置为0,每次从其他node接收到数据包时,如果发现发送者的epoch值比自己的大,那么当前node将自己的currentEpoch设置为发送者的epoch。由此,最终所有的nodes都会认同集群中最大的epoch值;当集群的状态变更,或者node为了执行某个行为需求agreement时,都将需要epoch(传递或者比较)。

configEpoch

每个master总会在ping、pong数据包中携带自己的configEpoch以及它持有的slots列表。新创建的node,其configEpoch为0。slaves在ping、pong数据包中也会携带自己的configEpoch信息,不过这个epoch为它与master在最近一次数据交换时,会更新为master的configEpoch。

每当节点发现configEpoch值变更时,都会将新值写入nodes.conf文件,当然currentEpoch也也是如此。这两个变量在写入文件后会伴随磁盘的fsync,持久写入。严格来说,集群中所有的master都持有唯一的configEpoch值。同一组master-slaves持有相同的configEpoch。

选举准备

当master处于FAIL状态时,将会触发slave的选举。slaves都希望将自己提升为master,此master的所有slaves都可以开启选举,不过最终只有一个slave获胜。
只有满足以下条件的slave拥有被选举权:

  • 这个节点是已下线主节点的从节点。

  • 已下线主节点负责处理的槽数量非空。

  • 从节点的数据被认为是可靠的,也即是,主从节点之间的复制连接(replication link)的断线时长不能超过节点超时时限(node timeout)乘以REDIS_CLUSTER_SLAVE_VALIDITY_MULT 常量得出的积。

一般来说,满足上述条件的从节点不会立即发起选举,而是会等待一个随机时间,然后才尝试选举,等待的时间:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

一定的延迟确保我们等待FAIL状态在集群中传播,否则其他masters或许尚未意识到FAIL状态,可能会拒绝投票。延迟的时间是随机的,这用来“去同步”(desynchronize),避免slaves同时开始选举。

SLAVE_RANK表示此slave已经从master复制数据的总量的rank。当master失效时,slaves之间交换消息以尽可能的构建rank,持有replication offset最新的rank为0,第二最新的为1,依次轮推。由公式可见,持有最新数据的slave将会首先发起选举(理论上)。 如果一个持有较小rank的slave选举失败,其他slaves将会稍后继续。

Slave发起投票请求

为了选举,第一步,就是slave自增它的currentEpoch值,然后向其他masters请求投票(需求支持,votes)。

slave通过向其他masters传播“FAILOVER_AUTH_REQUEST”数据包,然后最长等待2倍的NODE_TIMEOUT时间,来接收反馈。

一旦一个master向此slave投票,将会响应“FAILOVER_AUTH_ACK”,此后在2 * NODE_TIMOUT时间内,它将不会向同一个master的slaves投票;虽然这对保证安全上没有必要,但是对避免多个slaves同时选举时有帮助的。slave将会丢弃那些epoch值小于自己的currentEpoch的AUTH_ACK反馈,即不会对上一次选举的投票计数。

一旦此slave获取了大多数master的ACKs,它将在此次选举中获胜;否则如果大多数master不可达(在2 * NODE_TIMEOUT)或者投票额不足,那么它的选举将会被中断,那么其他的slave将会继续尝试。

Master响应投票请求

当Master接收到slave的“FAILOVER_AUTH_REQUEST”请求后,开始投票。

master并不会尽力选举最合适的slave, 只是批准主动投票者。最合适的slave应该在其他slaves之前,首先发起选举。当master拒绝一个slave投票,并不会发出一个“否决”响应,而是简单的忽略。

Mater投票规则:

  • 此master只会对指定的epoch投票一次,并且拒绝对旧的epoch投票:每个master都持有一个lastVoteEpoch,将会拒绝AUTH_REQUEST中currentEpoch比lastVoteEpoch小的请求。当master响应投票时,将会把lastVoteEpoch保存在磁盘中。

  • 此slave的master处于FAIL状态时,master才会投票。

  • 如果slave的currentEpoch比此master的currentEpoch小,那么AUTH_REQUEST将会被忽略。因为master只会响应那些与自己的currentEpoch相等的请求。

  • master在2 * NODE_TIMEOUT超时之前,不会对同一个master的slave再次投票。这并不是严格需要,因为也不太可能两个slave在相同的epoch下同时赢得选举。不过,它确保当一个slave选举成功后,它(slave)有一段缓冲时间来通知其他的slaves,避免另一个slave赢得了新的一轮的选择,避免不必要的二次failover。

  • slave发送的configEpoch是其master的,还包括其master持有的slots;master不会向持有相同slots、但configEpoch只较低的slave投票。

选举成功

一旦,slave选举成功,它将获取一个新的、唯一的、自增的configEpoch值,此值比集群中任何masters持有的都要大,它开始宣称自己是master,并进行一下步骤:
1)通过 PONG 数据包(packet)告知其他节点,这个节点现在是主节点了。

2)通过 PONG 数据包告知其他节点,这个节点是一个已升级的从节点(promoted slave)。

3)接管(claiming)所有由已下线主节点负责处理的哈希槽。

4)显式地向所有节点广播一个 PONG 数据包,加速其他节点识别这个节点的进度,而不是等待定时的 PING / PONG 数据包。当前node不可达的那些节点,它们可以从其他节点的ping或者pong中获知信息(gossip),并重新配置。

5)所有其他节点都会根据新的主节点对配置进行相应的更新,特别地:

  • 所有被新的主节点接管的槽会被更新。

  • 已下线主节点的所有从节点会察觉到 PROMOTED 标志,并开始对新的主节点进行复制。

旧master节点恢复

旧的master(也可以为slave)离群,然后重新加入集群时,若其持有的所有slot被某一节点接管,旧master将放弃这些slots,成为空的节点;此后 将会被重新配置,成为其他新master的slave。若离群一段时间后重新加入集群,发现此前自己持有的slots已经被其他多个nodes接管, 在重新配置时,最终此旧节点上的slots将会被清空,那个窃取自己最后一个slot的node,将成为它的新master。

CATALOG
  1. 1. 心跳机制
    1. 1.1. 心跳时机
    2. 1.2. 心跳数据
    3. 1.3. 心跳处理
      1. 1.3.1. 新节点加入
      2. 1.3.2. Slots信息
      3. 1.3.3. Master slave信息
    4. 1.4. 节点Fail探测
    5. 1.5. 广播
  2. 2. 宕机处理
    1. 2.1. 节点失效检测
    2. 2.2. 节点宕机处理
    3. 2.3. 集群FAIL判断
  3. 3. 从节点选举
    1. 3.1. 集群currentEpoch
    2. 3.2. configEpoch
    3. 3.3. 选举准备
    4. 3.4. Slave发起投票请求
    5. 3.5. Master响应投票请求
    6. 3.6. 选举成功
    7. 3.7. 旧master节点恢复