集群模式

发布时间 2023-12-19 10:22:42作者: 梅丹隆

一、Redis Cluster

1、Redis集群方案的演变

  • 大规模数据存储系统都会面临的一个问题就是如何横向拓展。
  • 当你的数据集越来越大,一主多从的模式已经无法支撑这么大量的数据存储,于是你首先考虑将多个主从模式结合在一起对外提供服务,但是这里有两个问题就是如何实现数据分片的逻辑和在哪里实现这部分逻辑?
  • 业界常见的解决方案有两种
    • 一是引入 _Proxy_ 层来向应用端屏蔽身后的集群分布,客户端可以借助 _Proxy_ 层来进行请求转发和 _Key_ 值的散列从而进行进行数据分片,这种方案会损失部分性能但是迁移升级等运维操作都很方便,业界 _Proxy_ 方案的代表有 Twitter 的_ Twemproxy 和豌豆荚的 _Codis
    • 二是 _smart client__ _方案,即将 _Proxy_ 的逻辑放在客户端做,客户端根据维护的映射规则和路由表直接访问特定的 Redis 实例,但是增减 Redis 实例都需要重新调整分片逻辑,如何使得客户端感知到集群的变化从而调整内存中维护的路由表呢,要么定时去感知,要么引入一个第三方协调服务,常见的就是 _Zookeeper_

2、Redis Cluster 简介

  • Redis 3.0 版本开始官方正式支持集群模式

  • Redis 集群模式提供了一种能将数据在多个节点上进行分区存储的方法,采取了和上述两者不同的实现方案——去中心化的集群模式

    • 集群通过分片进行数据共享,分片内采用一主多从的形式进行副本复制,并提供复制和故障恢复功能。
    • 在官方文档 Redis Cluster Specification 中,作者详细介绍了官方集群模式的设计考量,主要有如下几点:
      | 性能 | Redis 集群模式采用去中心化的设计,即 P2P 而非之前业界衍生出的 Proxy 方式 |
      | --- | --- |
      | 一致性 | _**master**__**slave**_ 之间采用异步复制,存在数据不一致的时间窗口,保证高性能的同时牺牲了部分一致性 |
      | 水平扩展 | 文中称可以线性扩展至 1000 个节点 |
      | 可用性 | 在集群模式推出之前,主从模式的可用性要靠 _**Sentinel**_ 保证,集群模式引入了新的故障检测机制,而在故障转移这块复用了 _**Sentinel**_ 的代码逻辑,不需要单独启动一个 Sentinel 集群,Redis Cluster本身就能自动进行 _**master**_ 选举_**failover**_ |
  • 下图是一个三主三从的 Redis Cluster

  • 三机房部署(其中一主一从构成一个分片,之间通过异步复制同步数据,一旦某个机房掉线,则分片上位于另一个机房的 _**slave**__** **_会被提升为 _**master**_ 从而可以继续提供服务)

  • 每个 _**master**_ 负责一部分 _**slot**_,数目尽量均摊;客户端对于某个 _**Key**_ 操作先通过公式计算(计算方法见下文)出所映射到的 _**slot**_,然后直连某个分片,写请求一律走 _**master**_,读请求根据路由规则选择连接的分片节点,对于 Squirrel 的客户端路由规则可见 通用-Squirrel_路由策略简介

image.png

3、三种集群方案的优缺点

集群模式 优点 缺点
客户端分片
- 不使用第三方中间件,实现方法和代码可以自己掌控并且可随时调整。
- 这种分片性能比代理式更好(因为少了分发环节),分发压力在客户端,无服务端压力增加

- 不能平滑地水平扩容,扩容/缩容时,必须手动调整分片程序
- 出现故障不能自动转移,难以运维
代理层分片
- 运维成本低。
- 业务方不用关心后端 Redis 实例,跟操作单点 Redis 实例一样。
- Proxy 的逻辑和存储的逻辑是隔离的

- 代理层多了一次转发,性能有所损耗;
- 进行扩容/缩容时候,部分数据可能会失效,需要手动进行迁移,对运维要求较高,而且难以做到平滑的扩缩容;
- 出现故障,不能自动转移,运维性很差。Codis 做了诸多改进,相比于 Twemproxy可用性和性能都好得多
Redis Cluster
- 无中心节点,数据按照 _**slot**_ 存储分布在多个 Redis 实例上
- 平滑的进行扩容/缩容节点,自动故障转移(节点之间通过 Gossip 协议交换状态信息,进行投票机制完成 _**slave**__**master**_ 角色的提升)降低运维成本,提高了系统的可扩展性和高可用性

- 开源版本缺乏监控管理
- 原生客户端太过简陋,_**failover**_ 节点的检测过慢,维护 _**Membership**__**Gossip**_ 消息协议开销大,无法根据统计区分冷热数据

二、哈希槽

1、什么是哈希槽

  • Redis Cluster中,数据分片借助哈希槽 (下文均称** **_**slot**_) 来实现
  • 集群预先划分 16384_**slot**_,对于每个请求集群的键值对,根据 _**Key**_ 进行散列生成的值唯一匹配一个 _**slot**_
  • Redis Cluster 中每个分片的_** _**master**_ 负责 16384_**slot**_ **_中的一部分
  • 当且仅当每个 _**slot**__** **_都有对应负责的节点时,集群才进入可用状态。
  • 当动态添加或减少节点时,需要将 16384_**slot**_ 做个再分配,_**slot**_ 中的键值也要迁移

2、哈希槽计算方法

HASH_SLOT = CRC16(key) mod 16384
  • 但是上述计算方法实际采用时,做了一些改变,改变的目的是为了支持哈希标签_**(Hash Tag)**_
  • 哈希标签是确保两个键都在同一个 _**slot**_ 里的一种方式。
  • 为了实现哈希标签,_**slot**_ 是用另一种不同的方式计算的。简单来说,如果一个键包含一个“{…}” 这样的模式,只有{ 和 } 之间的字符串会被用来做哈希以获取 slot。但是由于可能出现多个 { 或 },计算的算法如下
def HASH_SLOT(key)
    s = key.index "{"
    if s
        e = key.index "}",s+1
        if e && e != s+1
            key = key[s+1..e-1]
        end
    end
    crc16(key) % 16384
end

3、哈希槽的内部实现

  • Redis 集群中每个节点都会维护集群中所有节点的 _**clusterNode**_ 结构体
  • 其中的 _**slots**_ 属性是个二进制位数组,长度为 2048 bytes,共包含 16384_**bit**_ 位,节点可以根据某个 _**bit**_ 的 0/1 值判断对应的 _**slot**_ 是否由当前节点处理。
  • 每个节点通过 _**clusterStats**_ 结构体来保存从自身视角看去的集群状态,其中 _**nodes**_ 属性是一个保存节点名称和 _**clusterNode**_ 指针的字典,而 _**slots**_ 数组是一个记录哪个 _**slot**_ 属于哪个 _**clusterNode**_ 结构体的数组
typedef struct clusterState {
    ... ...
      // 保存集群节点的字典,键是节点名字,值是clusterNode结构的指针
      dict *nodes;          /* Hash table of name -> clusterNode structures */
    // 槽和负责槽节点的映射
    clusterNode *slots[CLUSTER_SLOTS];
    ... ...
    } clusterState;
typedef struct clusterNode {
  ... ...
    unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
    int numslots;   /* Number of slots handled by this node */
  ... ...
} clusterNode;

完整的映射关系如图
image.png

4、哈希槽的迁移

  • 线上集群因为扩容和缩容操作,经常需要迁移 _slot__ _对数据进行重新分片
  • 原生的 Redis Cluster 可以借助 redis-trib 工具进行迁移。Squirrel 使用自研的 Squirrel migrate 进行数据迁移和分片 _rebalance_
  • slot 在迁移过程有两个状态,在迁出节点会对该 _slot_ 标记为 _MIGRATING_,在迁入节点会对该 _slot_ 标记为 _IMPORTING_
  • 当该 _slot_ 内的 _Key_ 都迁移完毕之后,新的 _slot_ 归属信息都进过消息协议进行传播,最终集群中所有节点都会知道该 _slot_ 已经迁移到了目标节点,并更新自身保存的 slot 和节点间的映射关系。

三、MOVED & ASK

redis-cli 是官方提供的客户端脚本,我们可以通过 redis-cli -c -p port 命令连接任意一个 master,开始使用集群

1、详解MOVED

  • 我们通过 _redis-cli__ _可以发起对集群的读写请求,节点会计算我们请求的 Key 所属的 _slot_
  • 一旦发现该 _slot_ 并非由自己负责的话,会向客户端返回一个 _MOVED_ 错误(需要注意的是集群模式下 _redis-cli_不会打印 _MOVED_ 错误而是会直接显示 _Redirected_,使用单机版 _redis-cli_连接则可以看到 _MOVED_ 错误)
  • 指引客户端重定向到正确的节点,并再次发送先前的命令,得到正确的结果
# cluster 模式
10.72.227.3:6380> set gfdsdf sdf
-> Redirected to slot [6901] located at 10.72.227.2:6381
OK
# stand alone 模式
192.168.0.16:6379> set myKey myValue
(error) MOVED 16281 192.168.0.14:6379
192.168.0.16:6379> get myKey
(error) MOVED 16281 192.168.0.14:6379

2、详解ASK

  • _MOVED_ 意为这个 _slot_ 的负责已经永久转交给另一个节点,因此可以直接把请求准发给现在负责该 _slot_ 的节点。
  • 但是考虑在 _slot_ 迁移过程中,会出现属于该 _slot_ 的一部分 Key 已经迁移到目的地节点,而另一部分 Key 还在源节点
  • 那如果这时收到了关于这个 _slot_ 的请求,那么源节点会现在自己的数据库里查找是否有这个 Key,查到的话说明还未迁移那么直接返回结果,查询失败的话就说明 Key 已经迁移到目的地节点,那么就向客户端返回一个 _ASK_ 错误,指引客户端转向目的地节点查询该 Key
  • 同样该错误仅在单机版 redis-cli 连接时打印。

3、客户端处理

  • 这两个错误在实际线上环境中出现频率很高,那么定制化的客户端如何处理这二者呢?
  • 如果客户端每次都随机连接一个节点然后利用 _MOVED_ 或者 _ASK_ 来重定向其实是很低效的
  • 所以一般客户端会在启动时通过解析_ _CLUSTER NODES_ _或者 _CLUSTER SLOTS_ 命令返回的结果得到 _slot_ 和节点的映射关系缓存在本地
  • 一旦遇到 _MOVED_ 或者 _ASK_ 错误时会再次调用命令刷新本地路由(因为线上集群一旦出现 _MOVED_ 或者是 _ASK_ 往往是因为扩容分片导致数据迁移,涉及到许多 _slot_ 的重新分配而非单个,因此需要整体刷新一次)
  • 这样集群稳定时可以直接通过本地路由表迅速找到需要连接的节点。

四、故障检测

  • 跟大多数分布式系统一样,Redis Cluster 的节点间通过持续的 _heart beat_ 来保持信息同步

  • 不过 Redis Cluster 节点信息同步是内部实现的,并不依赖第三方组件如 _Zookeeper_

  • 集群中的节点持续交换 _PING__PONG_ 数据,消息协议使用_ _Gossip,这两种数据包的数据结构一样,之间通过 _type_ 字段进行区分。

  • Redis 集群中的每个节点都会定期向集群中的其他节点发送 _PING_ 消息,以此来检测对方是否存活

  • 如果接收 _PING_ 消息的节点在规定时间内(_node_timeout_)没有回复 _PONG_ 消息,那么之前向其发送 _PING_ 消息的节点就会将其标记为疑似下线状态(_PFAIL_)。

  • 每次当节点对其他节点发送 _PING_ 命令的时候,它都会随机地广播三个它所知道的节点的信息,这些信息里面的其中一项就是说明节点是否已经被标记为 _PFAIL_ 或者 _FAIL_

  • 当节点接收到其他节点发来的信息时,它会记下那些被集群中其他节点标记为 _PFAIL_ 的节点,这称为失效报告(_failure report_)。

  • 如果节点已经将某个节点标记为 _PFAIL_ ,并且根据自身记录的失效报告显示,集群中的大部分 _master_ 也认为该节点进入了 _PFAIL_ 状态,那么它会进一步将那个失效的 _master_ 的状态标记为 _FAIL_

  • 随后它会向集群广播 “该节点进一步被标记为 FAIL ” 的这条消息,所有收到这条消息的节点都会更新自身保存的关于该 _master__ _节点的状态信息为 _FAIL_

五、故障转移(Failover)

1、纪元(epoch)

Redis Cluster 使用了类似于 _Raft_ 算法 _term_(任期)的概念称为 _epoch_(纪元),用来给事件增加版本号。Redis 集群中的纪元主要是两种:_currentEpoch__configEpoch_

1.1、currentEpoch

  • 这是一个集群状态相关的概念,可以当做记录集群状态变更的递增版本号。

  • 每个集群节点,都会通过 server.cluster->currentEpoch 记录当前的 _currentEpoch_

  • 集群节点创建时,不管是 _master_ 还是 _slave_,都置 _currentEpoch_ 为 0。

  • 当前节点接收到来自其他节点的包时,如果发送者的 _currentEpoch_(消息头部会包含发送者的 _currentEpoch_)大于当前节点的_currentEpoch_,那么当前节点会更新 _currentEpoch_ 为发送者的 _currentEpoch_

  • 因此,集群中所有节点的 _currentEpoch_ 最终会达成一致,相当于对集群状态的认知达成了一致。

1.2、currentEpoch 作用

  • _currentEpoch_ 作用在于,当集群的状态发生改变,某个节点为了执行一些动作需要寻求其他节点的同意时,就会增加 _currentEpoch_ 的值。
  • 目前 _currentEpoch_ 只用于 _slave_ 的故障转移流程,这就跟哨兵中的sentinel.current_epoch 作用是一模一样的。
  • _slave A_ 发现其所属的 _master_ 下线时,就会试图发起故障转移流程。
    • 首先就是增加 _currentEpoch_ 的值,这个增加后的 _currentEpoch_ 是所有集群节点中最大的。
    • 然后_slave A_ 向所有节点发起拉票请求,请求其他 _master_ 投票给自己,使自己能成为新的 _master_
    • 其他节点收到包后,发现发送者的 _currentEpoch_ 比自己的 _currentEpoch_ 大,就会更新自己的 _currentEpoch_,并在尚未投票的情况下,投票给 _slave A_,表示同意使其成为新的 _master_

1.3、configEpoch

  • 这是一个集群节点配置相关的概念,每个集群节点都有自己独一无二的 configepoch

  • 所谓的节点配置,实际上是指节点所负责的槽位信息。

  • 每一个 _master_ 在向其他节点发送包时,都会附带其 _configEpoch_ 信息,以及一份表示它所负责的 _slots_ 信息。

  • _slave_ 向其他节点发送包时,其包中的 _configEpoch_ 和负责槽位信息,是其 _master__configEpoch_ 和负责的 _slot_ 信息。

  • 节点收到包之后,就会根据包中的 _configEpoch_ 和负责的 _slots_ 信息,记录到相应节点属性中

1.4、configEpoch 作用

  • _configEpoch_ 主要用于解决不同的节点的配置发生冲突的情况。
  • 举个例子就明白了:
    • 节点A 宣称负责 _slot 1_,其向外发送的包中,包含了自己的 _configEpoch_ 和负责的 _slots_ 信息。
    • 节点C 收到 A 发来的包后,发现自己当前没有记录 _slot 1_ 的负责节点(也就是 server.cluster->slots[1] 为 NULL),就会将 A 置为 _slot 1_ 的负责节点(server.cluster->slots[1] = A),并记录节点 A 的 _configEpoch_
    • 后来,节点 C 又收到了 B 发来的包,它也宣称负责 _slot 1_,此时,如何判断 _slot 1__ _到底由谁负责呢?
    • 这就是 _configEpoch_ 起作用的时候了,CB 发来的包中,发现它的 _configEpoch_,要比 A 的大,说明 B 是更新的配置。
    • 因此,就将 _slot 1_ 的负责节点设置为 B(server.cluster->slots[1] = B)。
    • _slave_ 发起选举,获得足够多的选票之后,成功当选时,也就是 _slave_ 试图替代其已经下线的旧 _master_,成为新的 _master_ 时,会增加它自己的 _configEpoch_,使其成为当前所有集群节点的 _configEpoch_ 中的最大值。
    • 这样,该 _slave_ 成为 _master_ 后,就会向所有节点发送广播包,强制其他节点更新相关 _slots_ 的负责节点为自己。

2、Failover

2.1、自动 Failover

  • 当一个 _slave_ 发现自己正在复制的 _master_ 进入了已下线(_FAIL_)状态时,_slave_ 将开始对已下线状态的 _master_ 进行故障转移,以下是故障转移执行的步骤
  • 该下线的 _master_ 下所有 _slave_ 中,会有一个 _slave_ 被选中。
    • 具体的选举流程为:
    • slave 自增它的 _currentEpoch_ 值,然后向其他 _masters_ 请求投票,每个 _slave_ 都向集群其他节点广播一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 消息用于拉票
    • 集群中具有投票权的 _master_ 收到消息后,如果在当前选举纪元中没有投过票,就会向第一个发送来消息的 _slave_ 返回 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,表示投票给该 _slave_
    • 某个 _slave_ 如果在一段时间内收到了大部分 _master_ 的投票,则表示选举成功。
  • 被选中的 _slave_ 会执行 SLAVEOF no one 命令,成为新的 _master_
  • 新的 _master_ 会撤销所有对已下线 _master__slot_ 指派,并将这些 _slot_ 全部指派给自己
  • 新的 _master_ 向集群广播一条 _PONG_ 消息,这条 _PONG_ 消息可以让集群中的其他节点立即知道自己已经由 _slave_ 变成了 _master_ ,并且这个 _master_ 已经接管了原本由已下线节点负责处理的 _slot_
  • 新的 _master_ 开始接收和自己负责处理的 _slot_ 有关的命令请求,故障转移完成

2.2、手动Failover

  • Redis 集群支持手动故障转移,也就是向 _slave_ 发送 CLUSTER FAILOVER 命令,使其在 master 未下线的情况下,发起故障转移流程,升级为新的 _master_ ,而原来的 _master_ 降级为 _slave_
  • 为了不丢失数据,向 _slave_ 发送 CLUSTER FAILOVER 命令后,流程如下:
    1. _slave_ 收到命令后,向 _master_ 发送 CLUSTERMSG_TYPE_MFSTART 命令
    2. _master_ 收到该命令后,会将其所有客户端置于阻塞状态,也就是在 10s 的时间内,不再处理客户端发来的命令,并且在其发送的心跳包中,会带有 CLUSTERMSG_FLAG0_PAUSED 标记
    3. _slave_ 收到 _master_ 发来的,带 CLUSTERMSG_FLAG0_PAUSED 标记的心跳包后,从中获取 _master_ 当前的复制偏移量,_slave_ 等到自己的复制偏移量达到该值后,才会开始执行故障转移流程:发起选举、统计选票、赢得选举、升级为 _master_ 并更新配置
  • CLUSTER FAILOVER 命令支持两个选项:_FORCE__TAKEOVER_。使用这两个选项,可以改变上述的流程。
    • 如果有 _FORCE_ 选项,则 _slave_ 不会与 _master_ 进行交互,_master_ 也不会阻塞其客户端,而是 _slave_ 立即开始故障转移流程:发起选举、统计选票、赢得选举、升级为 master 并更新配置。
    • 如果有 _TAKEOVER_ 选项,则更加简单直接,_slave_ 不再发起选举,而是直接将自己升级为 _master_ ,接手原 _master__slot_,增加自己的 _configEpoch_ 后更新配置。
  • 因此,使用 _FORCE__TAKEOVER_ 选项,master 可以已经下线;而不使用任何选项,只发送 CLUSTER FAILOVER 命令的话,_master_ 必须在线

六、集群

1、集群消息

  • 搭建 Redis Cluster 时,首先通过 CLUSTER MEET 命令将所有的节点加入到一个集群中,但是并没有在所有节点两两之间都执行 CLUSTER MEET 命令,因为节点之间使用 _Gossip_ 协议进行工作。
  • _Gossip_ 翻译过来就是流言,类似与病毒传播一样,只要一个人感染,如果时间足够,那么和被感染的人在一起的所有人都会被感染,因此随着时间推移,集群内的所有节点都会互相知道对方的存在。
  • 在 Redis 集群中,节点信息是如何传播的呢?
  • 答案是通过发送 PINGPONG 消息时,会包含节点信息,然后进行传播的。
  • 先介绍一下 Redis Cluster 中,消息是如何抽象的。一个消息对象可以是 _PING__PONG__MEET_,也可以是 _PUBLISH__FAIL_ 等。他们都是 clusterMsg 类型的结构,该类型主要由消息包头部和消息数据组成。
    • 消息包头部包含签名、消息总大小、版本和发送消息节点的信息。
    • 消息数据则是一个联合体 union clusterMsgData,联合体中又有不同的结构体来构建不同的消息。

_PING__PONG__MEET_ 属于一类,是 clusterMsgDataGossip 类型的数组,可以存放多个节点的信息,该结构如下:

/* Initially we don't know our "name", but we'll find it once we connect
* to the first node, using the getsockname() function. Then we'll use this
* address for all the next messages. */
typedef struct {
    // 节点名字
    char nodename[CLUSTER_NAMELEN];
    // 最近一次发送PING的时间戳
    uint32_t ping_sent;
    // 最近一次接收PONG的时间戳
    uint32_t pong_received;
    // 节点的IP地址
    char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
    // 节点的端口号
    uint16_t port; /* port last time it was seen */
    // 节点的标识
    uint16_t flags; /* node->flags copy */
    // 未使用
    uint16_t notused1; /* Some room for future improvements. */
    uint32_t notused2;
} clusterMsgDataGossip;
  • 每次发送 MEET、PING、PONG 消息时,发送者都从自己的已知节点列表中随机选出两个节点(可以是主节点或者从节点),并将这两个被选中节点的信息分别保存到两个结构中。
  • 当接收者收到消息时,接收者会访问消息正文中的两个结构,并根据自己是否认识 clusterMsgDataGossip 结构中记录的被选中节点进行操作:
    1. 如果被选中节点不存在于接收者的已知节点列表,那么说明接收者是第一次接触到被选中节点,接收者将根据结构中记录的IP地址和端口号等信息,与被选择节点进行握手。
    2. 如果被选中节点已经存在于接收者的已知节点列表,那么说明接收者之前已经与被选中节点进行过接触,接收者将根据 clusterMsgDataGossip 结构记录的信息,对被选中节点对应的 clusterNode 结构进行更新。
  • 有了消息之后,如何选择发送消息的目标节点呢?
  • 虽然 _PING__ _PONG_ _发送的频率越高就可以越实时得到其它节点的状态数据,但 _Gossip_ 消息体积较大,高频发送接收会加重网络带宽和消耗 CPU 的计算能力,因此每次 Redis 集群都会有目的性地选择一些节点;但节点选择过少又会影响故障判断的速度,Redis 集群的 Gossip 协议选择这样的解决方案:
    • 集群内每个节点维护定时任务默认为每秒执行10次,每秒会随机选取 5 个节点,找出最久没有通信的节点发送 _PING_ 消息,用来保证信息交换的随机性。
    • 每 100 毫秒都会扫描本地节点列表,如果发现节点最近一次接受 _PONG_ 消息的时间大于 cluster-node-timeout/2 则立刻发送 _PING_ 消息,这样做目的是防止该节点信息太长时间没更新,当我们宽带资源紧张时,可在 redis.confcluster-node-timeout 15000 改成 30 秒,但不能过度加大。

2、集群数据一致性

Redis 集群尽可能保证数据的一致性,但在特定条件下会丢失数据,原因有两点:异步复制机制以及可能出现的网络分区造成脑裂问题

2.1、异步复制

_master_ 以及对应的 _slaves_ 之间使用异步复制机制,考虑如下场景:

  • 写命令提交到 _master__master_ 执行完毕后向客户端返回 OK
  • 但由于复制的延迟此时数据还没传播给 _slave_;如果此时 master 不可达的时间超过阀值,此时集群将触发 _failover_,将对应的 _slave_ 选举为新的_master_,此时由于该 _slave_ 没有收到复制流,因此没有同步到 _slave_ 的数据将丢失

2.2、脑裂(split-brain)

在发生网络分区时,有可能出现新旧 _master_ 同时存在的情况,考虑如下场景:

  • 由于网络分区,此时 _master_ 不可达,且客户端与 _master_ 处于一个分区,并且由于网络不可达,此时客户端仍会向 _master_ 写入。
  • 由于 _failover_ 机制,将其中一个 _slave_ 提升为新的 _master_
  • 等待网络分区消除后,老的 _master__ 再次可达,但此时该节点会被降为 _slave_ 清空自身数据然后复制新的 _master_ _
  • 而在这段网络分区期间,客户端仍然将写命令提交到老的 _master_,但由于被降为 _slave_ 角色这些数据将永远丢失

image.png