018.Redis Cluster故障转移原理

1. 故障发现

当集群内某个节点出现问题时,需要通过一种健壮的方式保证识别出节点是否发生了故障。Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(PFAIL-Possibly Fail)客观下线(Fail)

  • 主观下线:指某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
  • 客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。

一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。所以集群还得经过一次协商的过程,只有当大多数节点都认定了某个节点失联了,集群才认为该节点需要进行主从切换来容错。Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了(PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集群的大多数,就可以标记该节点为确定下线状态 (Fail),然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实,并立即对该失联节点进行主从切换。

1.1 主观下线

集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(PFail)状态

  • 节点a发送ping消息给节点b,如果通信正常将接收到pong消息,节点a更新最近一次与节点b的通信时间。

  • 如果节点a与节点b通信出现问题则断开连接,下次会进行重连。如果一直通信失败,则节点a记录的与节点b最后通信时间将无法更新。

  • 节点a内的定时任务检测到与节点b最后通信时间超过cluster-node-timeout时,更新本地对节点b的状态为主观下线(pfail)。

主观下线简单来讲就是,当cluster-note-timeout时间内某节点无法与另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态

1.2 客观下线

Redis集群对于节点最终是否故障判断非常严谨,只有一个节点认为主观下线并不能准确判断是否故障。当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播,通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流
程。

为什么必须是负责槽的主节点参与故障发现决策?

因为集群模式下只有处理槽的主节点才负责读写请求和集群槽等关键信息维护,而从节点只进行主节点数据和状态信息的复制。

为什么半数以上处理槽的主节点?

必须半数以上是为了应对网络分区等原因造成的集群分割情况,被分割的小集群因为无法完成从主观下线到
客观下线这一关键过程,从而防止小集群完成故障转移之后继续对外提供服务。

客观下线流程:

  • 当消息体内含有其他节点的pfail状态会判断发送节点的状态,如果发送节点是主节点则对报告的pfail状态处理,从节点则忽略。
  • 找到pfail对应的节点,更新其内部下线报告(其中记录了每个节点对该节点做出的下线判断)
  • 根据更新后的下线报告链表告尝试进行客观下线
  • 每个节点都维护一个都下线报告,保存了其他主节点针对当前节点的下线报告
  • 下线报告中保存了报告故障的节点和最近收到下线报告的时间
  • 每个下线报告都存在有效期,每次在尝试触发客观下线时,都会检测下线报告是否过期,对于过期的下线报告将被删除。如果在cluster-node-time*2的时间内该下线报告没有得到更新则过期并删除
  • 下线报告的有效期限是cluster_node_timeout*2,主要是针对故障误报的情况。例如节点A在上一小时报告节点B主观下线,但是之后又恢复正常。现在又有其他节点上报节点B主观下线,根据实际情况之前的属于误
    报不能被使用
  • 统计有效的下线报告数量,如果小于集群内持有槽的主节点总数的一半则退出。
  • 当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。
  • 向集群广播一条fail消息,通知所有的节点将故障节点标记为客观下线,fail消息的消息体只包含故障节点的ID

注意:

如果在cluster-node-time*2时间内无法收集到一半以上槽节点的下线报告,那么之前的下线报告将会过期,也就是说主观下线上报的速度追赶不上下线报告过期的速度,那么故障节点将永远无法被标记为客观下线从而导致
故障转移失败。因此不建议将cluster-node-time设置得过小

广播fail消息是客观下线的最后一步,它承担着非常重要的职责:

  • 通知集群内所有的节点标记故障节点为客观下线状态并立刻生效。

  • 通知故障节点的从节点触发故障转移流程。

需要理解的是,尽管存在广播fail消息机制,但是集群所有节点知道故障节点进入客观下线状态是不确定的。比如当出现网络分区时有可能集群被分割为一大一小两个独立集群中。大的集群持有半数槽节点可以完成客观下线并广播fail消息,但是小集群无法接收到fail消息,网络分区会导致分割后的小集群无法收到大集群的fail消息,因此如果故障节点所有的从节点都在小集群内将导致无法完成后续故障转移,因此部署主从结构时需要根据自身机房/机架拓扑结构,降低主从被分区的可能性。

2. 故障恢复

故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程

  • 每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的主节点。如果从节点与主节点断线时间超过cluster-node-time*cluster-slave-validity-factor,则当前从节点不具备故障转移资格,cluster-slave-validity-factor设置为0代表任何slave都可以被转换为master,默认为10

  • 当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能执行后续流程,这里之所以采用延迟触发机制,主要是通过对多个从节点使用不同的延迟选举时间来支持优先级问题。复制偏移量越大说明从节点延迟越低,那么它应该具有更高的优先级来替换故障主节点,所有的从节点中复制偏移量最大的将提前触发故障选举流程

  • 当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程

    • 更新配置版本

      配置纪元是一个只增不减的整数,每个主节点自身维护一个配置版本(clusterNode.configEpoch)标示当前主节点的版本,所有主节点的配置版本都不相等,从节点会复制主节点的配置版本。整个集群又维护一个全局的配置版本(clusterState.current Epoch),用于记录集群内所有主节点配置版本的最大版本。执行cluster info命令可以查看配置版本信息

      10.0.0.102:6379> cluster info
      cluster_current_epoch:6
      cluster_my_epoch:4
      

      配置版本会跟随ping/pong消息在集群内传播,当发送方与接收方都是主节点且配置版本相等时代表出现了冲突,nodeId更大的一方会递增全局配置版本并赋值给当前节点来区分冲突

      配置版本的主要作用:

      • 标示集群内每个主节点的不同版本和当前集群最大的版本
      • 每次集群发生重要事件时,这里的重要事件指出现新的主节点(新加入的或者由从节点转换而来),从节点竞争选举。都会递增集群全局的配置版本并赋值给相关主节点,用于记录这一关键事件。
      • 主节点具有更大的配置版本代表了更新的集群状态,因此当节点间进行ping/pong消息交换时,如出现slots等关键信息不一致时,以配置版本更大的一方为准,防止过时的消息状态污染集群。
      • 配置版本的应用场景有:
        新节点加入
        槽节点映射冲突检测
        从节点投票选举冲突检测
      • 在通过cluster setslot命令修改槽节点映射时,需要确保执行请求的主节点本地配置版本是最大值,否则修改后的槽信息在消息传播中不会被拥有更高的配置版本的节点采纳。由于Gossip通信机制无法准确知道当前最大的配置版本在哪个节点,因此在槽迁移任务最后的cluster setslot {slot} node {nodeId}命令需要在全部主节点中执行一遍。
      • 从节点每次发起投票时都会自增集群的全局配置版本,并单独保存clusterState.failover_auth_epoch变量中用于标识本次从节点发起选举的版本
    • 广播选举消息

      在集群内广播选举消息FAILOVER_AUTH_REQUEST,并记录已发送过消息的状态,保证该从节点在一个配置版本内只能发起一次选举

  • 选举投票

    只有持有槽的主节点才会处理故障选举消息FAILOVER_AUTH_REQUEST,因为每个持有槽的节点在一个配置版本内都有唯一的一张选票,当接到第一个请求投票的从节点消息时回复FAILOVER_AUTH_ACK消息作为投票,之后相同配置版本内其他从节点的选举消息将忽略

    投票过程其实是一个领导者选举的过程,如集群内有N个持有槽的主节点代表有N张选票。由于在每个配置版本内持有槽的主节点只能投票给一个从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。

    Redis集群没有直接使用从节点进行领导者选举(投票让支持槽节点的master来做,而不是多个slave之间的投票),主要因为从节点数必须大于等于3个才能保证凑够N/2+1个节点,将导致从节点资源浪费。使用集群内所有持有槽的主节点进行领导者选举,即使只有一个从节点也可以完成选举过程。

    当从节点收集到N/2+1个持有槽的主节点投票时,从节点可以执行替换主节点操作,例如集群内有5个持有槽的主节点,主节点b故障后还有4个,当其中一个从节点收集到3张投票时代表获得了足够的选票可以进行替换主节点操作

    故障主节点也算在投票数内,假设集群内节点规模是3主3从,其中有2个主节点部署在一台机器上,当这台机器宕机时,由于从节点无法收集到3/2+1个主节点选票将导致故障转移失败。这个问题也适用于故障发现环节。因此部署集群时所有主节点最少需要部署在3台物理机上才能避免单点问题。

    投票作废:每个配置版本代表了一次选举周期,如果在开始投票之后的cluster-node-timeout*2时间内从节点没有获取足够数量的投票,则本次选举作废。其他从节点对配置版本自增并发起下一轮投票,直到选举成功为止

    Redis Cluster 可以为每个主节点设置若干个从节点,单主节点故障时,集群会自动将其中某个从节点提升为主节点。如果某个主节点没有从节点,那么当它发生故障时,集群将完全处于不可用状态。不过 Redis 也提供了一个参数cluster-require-full-coverage(默认yes) 可以允许部分节点故障,其它节点还可以继续提供对外访问。

  • 替换主节点

    当从节点收集到足够的选票之后,触发替换主节点操作:

    • 当前从节点取消复制变为主节点。
    • 执行clusterDelSlot操作撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽委派给自己
    • 向集群广播自己的pong消息,通知集群内所有的节点当前从节点变为主节点并接管了故障主节点的槽信息。

3. 故障转移时间

  • 主观下线(pfail)识别时间 = cluster-node-timeout

  • 主观下线状态消息传播时间 <= cluster-node-timeout/2,消息通信机制对超过cluster-node-timeout/2未通信节点会发起ping消息,消息体在选择包含哪些节点时会优先选取下线状态节点,所以通常这段时间内能够收集到半数以上主节点的pfail报告从而完成故障发现

  • 从节点转移时间<=1000毫秒,由于存在延迟发起选举机制,偏移量最大的从节点会最多延迟1秒发起选举。通常第一次选举就会成功,所以从节点执行转移时间在1秒以内

  • 根据以上分析可以预估出故障转移时间:failover-time ≤ (cluster-node-timeout * 1.5 + 1000)ms,因此,故障转移时间跟cluster-node-timeout参数息息相关,默认15秒,配置时可以根据业务容忍度做出适当调整,但不是越小越好

4. 故障转移演练

  • 一个master下线
root       3423      1  0 11:38 ?        00:01:06 bin/redis-server 10.0.0.100:6379 [cluster]
root       3428      1  0 11:38 ?        00:01:05 bin/redis-server 10.0.0.100:6380 [cluster]
root       3840   3004  0 17:09 pts/0    00:00:00 grep --color=auto redis
[root@node01 redis]# kill -9 3423
  • slave与下线master的主从复制中断
[root@node03 redis]# cat /var/log/redis/redis_6380.log

654:S 25 Mar 17:10:29.783 # Connection with master lost.
2654:S 25 Mar 17:10:29.784 * Caching the disconnected master state.
2654:S 25 Mar 17:10:29.784 * Connecting to MASTER 10.0.0.100:6379
2654:S 25 Mar 17:10:29.784 * MASTER <-> SLAVE sync started
2654:S 25 Mar 17:10:29.785 # Error condition on socket for SYNC: Connection refused
  • 其他两个master标记下线master主观下线
[root@node02 redis]# cat /var/log/redis/redis_6379.log
2876:M 25 Mar 17:10:45.391 * Marking node 9c02aef2d45e44678202721ac923c615dd8300ea as failing (quorum reached).

[root@node03 redis]# cat /var/log/redis/redis_6379.log
2649:M 25 Mar 17:10:45.411 * Marking node 9c02aef2d45e44678202721ac923c615dd8300ea as failing (quorum reached).
  • 超半数master认为下线master主观下线,所以下线master客观下线
  • slave节点在延迟724ms后,开始准备选举,它和下线master的复制偏移量是21930
2654:S 25 Mar 17:10:45.415 # Cluster state changed: fail
2654:S 25 Mar 17:10:45.510 # Start of election delayed for 724 milliseconds (rank #0, offset 21930).
  • slave更新配置版本并发起选举
2654:S 25 Mar 17:10:46.322 # Starting a failover election for epoch 7.
  • 其他两个master对slave进行了投票
2649:M 25 Mar 17:10:46.327 # Failover auth granted to 0955dc1eeeec59c1e9b72eca5bcbcd04af108820 for epoch 7
2876:M 25 Mar 17:10:46.310 # Failover auth granted to 0955dc1eeeec59c1e9b72eca5bcbcd04af108820 for epoch 7
  • 重启下线的master
[root@node01 redis]# bin/redis-server conf/redis_6379.conf
  • 旧master节点启动后发现自己负责的槽指派给另一个节点,则以现有集群配置为准,变为新主节点的从节点
3873:M 25 Mar 17:24:32.823 * Node configuration loaded, I'm 9c02aef2d45e44678202721ac923c615dd8300ea
873:M 25 Mar 17:24:32.825 # Configuration change detected. Reconfiguring myself as a replica of 0955dc1eeeec59c1e9b72eca5bcbcd04af108820
3873:S 25 Mar 17:24:32.825 * Before turning into a slave, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.
  • 集群内其他节点接收到新上线发来的ping消息,清空客观下线状态
3428:S 25 Mar 17:24:32.830 * Clear FAIL state for node 9c02aef2d45e44678202721ac923c615dd8300ea: master without slots is reachable again.
2876:M 25 Mar 17:24:32.914 * Clear FAIL state for node 9c02aef2d45e44678202721ac923c615dd8300ea: master without slots is reachable again.
2881:S 25 Mar 17:24:32.916 * Clear FAIL state for node 9c02aef2d45e44678202721ac923c615dd8300ea: master without slots is reachable again.
2654:M 25 Mar 17:24:32.853 * Clear FAIL state for node 9c02aef2d45e44678202721ac923c615dd8300ea: master without slots is reachable again.
2649:M 25 Mar 17:24:32.854 * Clear FAIL state for node 9c02aef2d45e44678202721ac923c615dd8300ea: master without slots is reachable again.
  • 新的主从开始复制
# slave
3873:S 25 Mar 17:24:33.832 * Connecting to MASTER 10.0.0.102:6380
3873:S 25 Mar 17:24:33.833 * MASTER <-> SLAVE sync started
3873:S 25 Mar 17:24:33.835 * Non blocking connect for SYNC fired the event.
3873:S 25 Mar 17:24:33.837 * Master replied to PING, replication can continue...
3873:S 25 Mar 17:24:33.840 * Trying a partial resynchronization (request b3a120153f855c5b200783267f6d88655d616318:1).
3873:S 25 Mar 17:24:33.843 * Full resync from master: 6b10906d0f362be8f9dfcb373c47d2ab44f8f805:21930

# master
2654:M 25 Mar 17:24:33.845 * Slave 10.0.0.100:6379 asks for synchronization
2654:M 25 Mar 17:24:33.845 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for 'b3a120153f855c5b200783267f6d88655d616318', my replication IDs are '6b10906d0f362be8f9dfcb373c47d2ab44f8f805' and 'e5a8131d602c8d58155a74b1bad17fae955431f1')
2654:M 25 Mar 17:24:33.846 * Starting BGSAVE for SYNC with target: disk
2654:M 25 Mar 17:24:33.846 * Background saving started by pid 3089
3089:C 25 Mar 17:24:33.851 * DB saved on disk
3089:C 25 Mar 17:24:33.852 * RDB: 0 MB of memory used by copy-on-write
2654:M 25 Mar 17:24:33.861 * Background saving terminated with success
2654:M 25 Mar 17:24:33.862 * Synchronization with slave 10.0.0.100:6379 succeeded
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341