前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >018.Redis Cluster故障转移原理

018.Redis Cluster故障转移原理

作者头像
CoderJed
发布2020-03-27 00:05:00
5K1
发布2020-03-27 00:05:00
举报
文章被收录于专栏:Jed的技术阶梯Jed的技术阶梯

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下线
代码语言:javascript
复制
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的主从复制中断
代码语言:javascript
复制
[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主观下线
代码语言:javascript
复制
[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
代码语言:javascript
复制
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更新配置版本并发起选举
代码语言:javascript
复制
2654:S 25 Mar 17:10:46.322 # Starting a failover election for epoch 7.
  • 其他两个master对slave进行了投票
代码语言:javascript
复制
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
代码语言:javascript
复制
[root@node01 redis]# bin/redis-server conf/redis_6379.conf
  • 旧master节点启动后发现自己负责的槽指派给另一个节点,则以现有集群配置为准,变为新主节点的从节点
代码语言:javascript
复制
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消息,清空客观下线状态
代码语言:javascript
复制
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.
  • 新的主从开始复制
代码语言:javascript
复制
# 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
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 故障发现
    • 1.1 主观下线
      • 1.2 客观下线
      • 2. 故障恢复
      • 3. 故障转移时间
      • 4. 故障转移演练
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档