您的位置:首页 > 数据库 > Redis

Redis集群

2017-09-13 18:25 78 查看
一、Redis Cluster主要特性和设计

⑴集群目标

①高性能和线性扩展,最大可以支撑到1000个节点;Cluster架构中无Proxy层,Master与slave之间使用异步replication,且不存在操作的merge。(即操作不能跨多个nodes,不存在merge层)

②一定程度上保证writes的安全性,需要客户端容忍一定程度的数据丢失:集群将会尽可能(best-effort)保存客户端write操作的数据;通常在failover期间,会有短暂时间内的数据丢失(因为异步replication引起);当客户端与少数派的节点处于网络分区时(network
partition),丢失数据的可能性会更高。(因为节点有效性检测、failover需要更长的时间)

③可用性:只要集群中大多数master可达、且失效的master至少有一个slave可达时,集群都可以继续提供服务;同时“replicas
migration”可以将那些拥有多个slaves的master的某个slave,迁移到没有slave的master下,即将slaves的分布在整个集群相对平衡,尽力确保每个master都有一定数量的slave备份。

 (Redis Cluster集群有多个shard组成,每个shard可以有一个master和多个slaves构成,数据根据hash
slots配额分布在多个shard节点上,节点之间建立双向TCP链接用于有效性检测、Failover等,Client直接与shard节点进行通讯;Cluster集群没有Proxy层,也没有中央式的Master用于协调集群状态或者state存储;集群暂不提供动态reblance策略)

⑵ Mutli-key操作

对于“multi-key”操作(即一次RPC调用中需要进行多个key的操作)比如Set类型的交集、并集等,则要求这些key必须属于同一个node。Cluster不能进行跨Nodes操作,也没有nodes提供merge层代理。

Cluster中实现了一个称为“hash
tags”的概念,每个key都可以包含一个自定义的“tags”,此特性,可以强制某些keys被保存在同一个节点上,以便于进行“multikey”操作,不过在人工对slots进行resharding期间,multikey操作可能不可用。

我们在Redis单例中,偶尔会用到“SELECT”指令,即可以将key保存在特定的database中(默认database索引号为0);但是在Cluster环境下,将不支持SELECT命令,所有的key都将保存在默认的database中。

⑶客户端与Server角色

集群中的每个节点都通过TCP与其他所有nodes建立连接,它们之间的通信协议和方式称为“Redis
Cluster Bus”。Nodes之间使用gossip协议(参见下文备注)向其他nodes传播集群信息,以达到自动发现的特性,通过发送ping来确认其他nodes工作正常,也会在合适的时机发送集群的信息。当然在Failover时(包括人为failover)也会使用Bus来传播消息。
因为Node并不提供Proxy机制,当Client将请求发给错误的nodes时(此node上不存在此key所属的slot),node将会反馈“MOVED”或者“ASK”错误信息,以便Client重新定向到合适的node。理论上,Client可以将请求发送给任意一个nodes,然后根据在根据错误信息转发给合适的node,客户端可以不用保存集群的状态信息,当然这种情况下性能比较低效,因为Client可能需要2次TCP调用才能获取key的结果,通常客户端会缓存集群中nodes与slots的映射关系,并在遇到“Redirected”错误反馈时,才会更新本地的缓存。
⑷安全写入
Redis 集群节点间使用异步副本备份,通常存在在一个时间窗口,可能在分片中丢失写入数据。 但是一个连接到绝大部分master节点的客户端的时间窗口,与一个连接到极小部分master节点的客户端的时间窗口有很大的区别。

Redis 集群会努力尝试保存所有与大多数master节点连接的客户端执行的写操作,但以下两种情况除外,会导致失败期间在多数分片丢失写操作:

①写入操作能到达一个master节点,但当master节点要回复客户端的时候,这个写入有可能没有通过master-slave异步备份传播到slave节点那里。 如果在某个写入操作没有到达slave节点的时候master节点已经宕机了,那么该写入会永远地丢失掉(如果master长时间周期不可达而它的slave升级成master)。

这通常在所有情况中很难发现,master突然发生故障的情况下,由于master尝试回复客户端(写入的应答)和slave(传播写操作)在大致相同时间。然而,它是一个现实世界的故障模式。

②另一个理论上可能会丢失写入操作的模式是:

master故障转移, 它的一个slave升级成了master。过一段时间之后这个节点再次变得可达。一个持有过期路由表的客户端或许会在集群把这个master节点变成一个slave节点(新master节点的slave节点)之前对它进行写入操作。

实际上这是极小概率事件,这是因为,那些由于长时间无法被大多数master节点访问到的节点会被故障转移掉,将不再接受任何写入操作,当其分区修复好以后仍然会在一小段时间内拒绝写入操作好让其他节点有时间被告知配置信息的变更。这种失效模式也需要客户端的路由表还没有被更新。

通常所有节点都会尝试尽快去访问一个再次加入到集群里的节点,一旦跟该节点建立一个新的连接就会发送一个ping包过去(这足够升级节点配置信息)。这就使得一个节点很难在恢复可写入状态之前没被告知配置信息更改。

写入操作到达少数分片会有更大的丢失窗口。比如:
Redis 集群在拥有少数master节点和至少一个客户端的分片上容易丢失为数不少的写入操作,这是因为,如果master节点被故障转移到集群中多数节点那边, 那么所有发送到这些master节点的写入操作可能会丢失。

⑸可用性
当每次failover发生后,集群都会重新配置、平衡slaves的分布,以更好的抵御下一次失效情况的发生

⑹性能
Redis
Cluster并没有提供Proxy层,而是告知客户端将key的请求转发给合适的nodes。Client保存集群中nodes与keys的映射关系(slots),并保持此数据的更新,所以通常Client总能够将请求直接发送到正确的nodes上。

因为采用异步replication,所以master不会等待slaves也保存成功后才向客户端反馈结果,除非显式的指定了WAIT指令。multi-key指令仅限于单个节点内,除了resharding操作外,节点的数据不会在节点间迁移。每个操作只会在特定的一个节点上执行,所以集群的性能为master节点的线性扩展。同时,Clients与每个nodes保持链接,所以请求的延迟等同于单个节点,即请求的延迟并不会因为Cluster的规模增大而受到影响。高性能和扩展性,同时保持合理的数据安全性,是Redis
Cluster的设计目标。

二、Cluster主要组件

⑴ keys分布模型(重要)
集群将key分成16384个slots(hash
槽),slot是数据映射的单位,言外之意,Redis Cluster最多支持16384个nodes,集群中的每个master持有16384个slots中的一部分,处于“stable”状态时,集群中没有任何slots在节点间迁移。

当需要在
Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,即HASH_SLOT =CRC16(key) mod 16384,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽。
⑵hash tags
 在计算hash
slots时有一个意外的情况,用于支持“hash tags”;hash tags用于确保多个keys能够被分配在同一个hash slot中,用于支持multi-key操作。hash tags的实现比较简单,key中“{}”之间的字符串就是当前key的hash tags,如果存在多个“{}”,首个符合规则的字符串作为hash
tags,如果“{}”存在多级嵌套,那么最内层首个完整的字符串作为hash tags,比如“{foo}.student”,那么“foo”是hash tags。如果key中存在合法的hash tags,那么在计算hash slots时,将使用hash tags,而不再使用原始的key。即“foo”与“{foo}.student”将得到相同的slot值,不过“{foo}.student”仍作为key来保存数据,即redis中数据的key仍为“{foo}.student”

⑶ 集群节点的属性
   集群中每个节点都有唯一的名字,称之为node ID,一个160位随机数字的16进制表示,在每个节点首次启动时创建。每个节点都将各自的ID保存在实例的配置文件中,此后将一直使用此ID,或者说只要配置文件不被删除,或者没有使用“CLUSTER
RESET”指令重置集群,那么此ID将永不会修改。
    集群通过node ID来标识节点,而不是使用IP + port,因为node可以修改它的IP和port,不过如果ID不变,我们仍然认定它是集群中合法一员。集群可以在cluster Bus中通过gossip协议来探测IP、port的变更,并重新配置。
    node ID并不是与node相关的唯一信息,不过是唯一一个全局一致的。每个node还持有如下相关的信息,有些信息是关系集群配置的,其他的信息比如最后ping时间等。每个node也保存其他节点的IP、Port、flags(比如flags表示它是master还是slave)、最近ping的时间、最近pong接收时间、当前配置的epoch、链接的状态,最重要的是还包含此node上持有的hash
slots。这些信息均可通过“CLUSTER NODES”指令开查看。
⑷Cluster
Bus

  每个redis集群需要开两个TCP端口,一个为客户端调用端口(可以叫命令端口,比如6379);另一个为集群通讯端口(也叫BUS端口),用于node之间通讯。
 
 集群通讯端口=客户端调用端口+10000。
⑸ 集群拓扑
   Redis
Cluster中每个node都与其他nodes的Bus端口建立TCP链接(full mesh,全网)。这些TCP链接总是keepalive,不是按需创建的。如果ping发出之后,node在足够长的时间内仍然没有pong响应,那么次node将会被标记为“不可达”,那么与此node的链接将会被刷新或者重建。Nodes之间通过gossip协议和配置更新的机制,来避免每次都交互大量的消息,最终确保在nodes之间的信息传送量是可控的。
⑹节点间handshake
  Nodes通过Bus端口发送ping、pong;如果一个节点不属于集群,那么它的消息将会被其他nodes全部丢弃。一个节点被认为是集群成员的方式有2种:
   ①如果此node在“Cluster
meet”指令中引入,此命令的主要意义就是将指定node加入集群。那么对于当前节点,将认为指定的node为“可信任的”。(此后将会通过gossip协议传播给其他nodes)

  ②当其他nodes通过gossip引入了新的nodes,这些nodes也是被认为是“可信任的”。

三、重定向与resharding

⑴MOVED重定向

    理论上,Client可以将请求随意发给任何一个node,包括slaves,此node解析query,它会查看key应该属于哪个slot、以及此slot所在的nodes,如果当前node持有此slot,那么query直接执行即可,否则当前node将会向Client反馈“MOVED”错误:

GET X
-MOVED 3999 127.0.0.1:6381


   错误信息中包括此key对应的slot(3999),以及此slot所在node的ip和port,对于Client 而言,收到MOVED信息后,它需要将请求重新发给指定的node。不过,当node向Client返回MOVED之前,集群的配置也在变更(节点调整、resharding、failover等,可能会导致slot的位置发生变更),此时Client可能需要等待更长的时间,不过最终node会反馈MOVED信息,且信息中包含指定的新的node位置。虽然Cluster使用ID标识node,但是在MOVED信息中尽可能的暴露给客户端便于使用的ip
+ port。

   当Client遇到“MOVED”错误时,将会使用“CLUSTER NODES”或者“CLUSTER SLOTS”指令获取集群的最新信息,主要是nodes与slots的映射关系;因为遇到MOVED,一般也不会仅仅一个slot发生的变更,通常是一个或者多个节点的slots发生了变化,所以进行一次全局刷新是有必要的;我们还应该明白,Client将会把集群的这些信息缓存,以便提高query的性能。

   还有一个错误信息:“ASK”,它与“MOVED”都属于重定向错误,客户端的处理机制基本相同,只是ASK不会触发Client刷新本地的集群信息。
⑵集群运行时重新配置(live reconfiguration)


①集群中新增一个node,我们需要将其他nodes上的部分slots迁移到此新nodes上,以实现数据负载的均衡分配。

②集群中移除一个node,那么在移除节点之前,必须将此节点上(如果此节点没有任何slaves)的slots迁移到其他nodes。

③如果数据负载不均衡,比如某些slots数据集较大、负载较大时,我们需要它们迁移到负载较小的nodes上(即手动resharding),以实现集群的负载平衡。

 Cluster支持slots在nodes间移动;从实际的角度来看,一个slot只是一序列keys的逻辑标识,所以Cluster中slot的迁移,其实就是一序列keys的迁移,不过resharding操作只能以slot为单位(而不能仅仅迁移某些keys)。Redis提供了如下几个操作:

1)CLUSTER ADDSLOTS [slot] ....

2)CLUSTER DELSLOTS [slot] ...

3)CLUSTER SETSLOT [slot] NODE [node]

4)CLUSTER SETSLOT [slot] MIGRATING [destination-node]

5)CLUSTER SETSLOT [slot] IMPORTING [source-node]
①CLUSTER ADDSLOTS [slot] ....
可接受多个值,创建新的集群或者修复一个broken的集群(集群中某些slots因为nodes的永久失效而丢失)时使用,一个新的集群有多个空的Masters构成,此后管理员需要手动为每个master分配slots,并将16384个slots分配完毕,集群才能正常服务。为了避免出错,Redis
Cluster提供了一个redis-trib辅助工具,方便我们做这些事情。

②DELSLOTS就是将指定的slots删除,前提是这些slots必须在当前node上,被删除的slots处于“未分配”状态(当然其对应的keys数据也被clear),即尚未被任何nodes覆盖,这种情况可能导致集群处于不可用状态,此指令通常用于debug,在实际环境中很少使用。那些被删除的slots,可以通过ADDSLOTS重新分配。

③ SETSLOT是个很重要的指令,对集群slots进行reshard的最重要手段;它用来将单个slot在两个nodes间迁移。根据slot的操作方式,它有两种状态“MIGRATING”、“IMPORTING”(或者说迁移的方式)

MIGRATING:将slot的状态设置为“MIGRATING”,并迁移到destination-node上,需要注意当前node必须是slot的持有者。在迁移期间,Client的查询操作仍在当前node上执行,如果key不存在,则会向Client反馈“-ASK”重定向信息,此后Client将会把请求重新提交给迁移的目标node。IMPORTING:将slot的状态设置为“IMPORTING”,并将其从source-node迁移到当前node上,前提是source-node必须是slot的持有者。Client交互机制同上。

假如我们有两个节点A、B,其中slot 8在A上,我们希望将8从A迁移到B,可以使用如下方式:

①在B上:CLUSTER SETSLOT 8 IMPORTING A  

②在A上:CLUSTER SETSLOT 8 MIGRATING B

在迁移期间,集群中其他的nodes的集群信息不会改变,即slot 8仍对应A,即此期间,Client查询仍在A上:

①如果key在A上存在,则有A执行。

②否则,将向客户端返回ASK,客户端将请求重定向到B。

这种方式下,新key的创建就不会在A上执行,而是在B上执行,这也就是ASK重定向的原因(迁移之前的keys在A,迁移期间created的keys在B上);当上述SETSLOT执行完毕后,slot的状态也会被自动清除,同时将slot迁移信息传播给其他nodes,至此集群中slot的映射关系将会变更,此后slot
8的数据请求将会直接提交到B上。

⑶ ASK重定向

    为什么我们不能单纯地使用 MOVED 重定向呢?因为收到 MOVED,意味着我们认为哈希槽永久地归属到了另一个节点,并且接下来的所有请求都尝试发到目标节点上去。而
ASK 意味着我们只要下一个请求发送到目标节点上去。

①当Client接收到ASK重定向,它仅仅将当前query重定向到指定的node;此后的请求仍然交付给旧的节点。

②客户端并不会更新本地的slots映射,仍然保持slot 8与A的映射;直到集群迁移完毕,且遇到MOVED重定向。

    一旦slot 8迁移完毕之后(集群的映射信息也已更新),如果Client再次在A上访问slot 8时,将会得到MOVED重定向信息,此后客户端也更新本地的集群映射信息。

⑷ 客户端首次链接以及重定向处理

Client应该尽可能的将slots配置信息缓存在本地,不过配置信息也不需要绝对的实时更新,因为在请求时偶尔出现“重定向”,Client也能兼容此次请求的正确转发,此时再更新slots配置。

遇到MOVED时,客户端仅仅更新特定的slot是不够的,因为集群中的reshard通常会影响到多个slots。客户端通过向任意一个nodes发送“CLUSTER
NODES”或者“CLUSTER SLOTS”指令均可以获得当前集群最新的slots映射信息;“CLUSTER SLOTS”指令返回的信息更易于Client解析。如果集群处于broken状态,即某些slots尚未被任何nodes覆盖,指令返回的结果可能是不完整的。

⑸通过slave节点水平扩展读

因为redis的slaves可以支持read操作(前提是application能够容忍失效数据),所以客户端可以使用“READONLY”指令来扩展read请求。

 “READONLY”表明其可以访问集群的slaves节点,能够容忍stale数据,而且此次链接不会执行writes操作。当链接设定为readonly模式后,Cluster只有当keys不被slave的master节点持有时才会发送重定向消息(即Client的read请求总是发给slave,只有当此slave的master不持有slots时才会重定向,很好理解):

①此slave的master节点不持有相应的slots

②集群重新配置,比如reshard或者slave迁移到了其他master上,此slave本身也不持有此slot。

此时Client更新本地的slot配置信息,同上文所述。(目前很多Client实现均基于连接池,所以不能非常便捷的设置READLONLY选项,非常遗憾)

四、容错

⑴心跳与gossip消息

    集群中的nodes持续的交换ping、pong数据,这两种数据包的结构一样,同样都能携带集群的配置信息,唯一不同的就是message中的type字段。

    通常,一个node发送ping消息,那么接收者将会反馈pong消息;不过有时候并非如此,或许接收者将pong信息发给其他的nodes,而不是直接反馈给发送者,比如当集群中添加新的node时。

    通常一个node每秒都会随机向几个nodes发送ping,所以无论集群规模多大,每个nodes发送的ping数据包的总量是恒定的。每个node都确保尽可能的向那些在半个NODE_TIMEOUT时间内,尚未发送过ping或者接收到它们的pong消息的nodes发送ping。在NODE_TIMEOUT逾期之前,nodes也会尝试与那些通讯异常的nodes重新建立TCP链接,确保不能仅仅因为当前链接异常而认为它们就是不可达的

   当NODE_TIMEOUT值较小、集群中nodes规模较大时,那么全局交换的信息量也会非常庞大,因为每个node都尽力在半个NODE_TIMEOUT时间内,向其他nodes发送ping。比如有100个nodes,NODE_TIMEOUT为60秒,那么每个node在30秒内向其他99各nodes发送ping,平均每秒3.3个消息,那么整个集群全局就是每秒330个消息。这些消息量,并不会对集群的带宽带来不良问题。

⑵心跳数据包的内容
1)node ID

2)currentEpoch和configEpoch

3)node flags:比如表示此node是maste、slave等

4)hash slots:发送者持有的slots

5)如果发送者是slave,那么其master的ID

6)其他..
ping和pong数据包中也包含gossip部分,这部分信息包含sender持有的集群视图,不过它只包含sender已知的随机几个nodes,nodes的数量根据集群规模的大小按比例计算。gossip部分包含了nodes的ID、ip+port、flags,那么接收者将根据sender的视图,来判定节点的状态,这对故障检测、节点自动发现非常有用。
⑶失效检测

    集群失效检测就是,当某个master或者slave不能被大多数nodes可达时,用于故障迁移并将合适的slave提升为master。当slave提升未能有效实施时,集群将处于error状态且停止接收Client端查询。

    如上所述,每个node有持有其已知nodes的列表包括flags,有2个flag状态:PFAIL和FAIL;PFAIL表示“可能失效”,是一种尚未完全确认的失效状态(即某个节点或者少数masters认为其不可达)。FAIL表示此node已经被集群大多数masters判定为失效(大多数master已认定为不可达,且不可达时间已达到设定值,需要failover)。
① PFAIL
    一个被标记为PFAIL的节点,表示此node不可达的时间超过NODE_TIMEOUT,master和slave有可以被标记为PFAIL,为了避免误判,当一个node在半个NODE_TIMEOUT时间内仍未能pong,那么当前node将会尽力尝试重新建立连接进行重试,以排除pong未能接收是因为当前链接故障的问题。
② FAIL
     PFAIL被上升为FAIL的情况:

    1)比如A节点,认为B为PFAIL

    2)那么A通过gossip信息,收集集群中大多数masters关于B的状态视图。

    3)多数master都认为B为PFAIL,或者PFAIL情况持续时间为NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT(此值当前为2),即两倍的NODE_TIMEOUT时间。

    如果上述条件成立,那么A将会:

    1)将B节点设定为FAIL

    2)将FAIL信息发送给其所有能到达的所有节点。

   FAIL状态是单向的,即PFAIL可以转换为FAIL,但是FAIL状态只能清除,不能回转为PFAIL

    清除FAIL 标识只有以下几种情况:
    1)当此node已经变的可达,且为slave节点,这种情况下FAIL状态将会被清除,因为没有发生failover。

   2)此node已经可达,且是一个没有服务任何slots的master(空的master);这种情况下,FAIL将会被清除,因为master没有持有slots,所以它并没有真正参与到集群中,需要等到重新配置以便它加入集群。

   3)此node已经可达,且是master,且在较长时间内(N倍的NODE_TIMEOUT)没有检测到slave的提升,即没有slave发生failover(比如此master下没有slave),那么它只能重新加入集群且仍为master。

五、配置处理,传播,和失效转移

⑴集群当前epoch

用来记录事件增量的版本号,在节点冲突时可以知道哪个节点是最新的。是一个64位无签名的数字。

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

⑵epoch配置(configEpoch)

    每个master总会在ping、pong数据包中携带自己的configEpoch以及它持有的slots列表。新创建的node,其configEpoch为0,slaves通过递增它们的configEpoch来替代失效的master,并尝试获得其他大多数master的授权(认同)。当slave被授权,一个新的configEpoch被生成,slave提升为master且使用此configEpoch。

    接下来介绍configEpoch帮助解决冲突,当不同的nodes宣称有分歧的配置时。

    slaves在ping、pong数据包中也会携带自己的configEpoch信息,不过这个epoch为它与master在最近一次数据交换时,master的configEpoch。

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

⑶ slave选举与提升

当如下情况下,开始slave选举:

①当前结点的master处于fail状态。

②此master拥有非0个slots

③此slave的replication链接与master断开时间没有超过设定值,这个时间用户可以配置。
选举步骤:
①slave自增它的currentEpoch值,然后向其他masters请求投票(需求支持,votes)

②slave节点通过广播一个 FAILOVER_AUTH_REQUEST 数据包给集群里的每个master节点来请求选票。然后等待回复

③一旦一个master节点给这个slave节点投票,会回复一个FAILOVER_AUTH_ACK,并且在 NODE_TIMEOUT
* 2 这段时间内不能再给同个master节点的其他slave节点投票。

④slave节点会忽视所有带有的epoch参数比 currentEpoch 小的回应(ACKs),这样能避免把之前的投票的算为当前的合理投票。

⑤一旦某个slave节点收到了大多数master节点的回应,如果超时或投票数不足,会在过一定时间重新发起。

⑷slave排名

当master处于FAIL状态时,slave将会随机等待一段时间,然后才尝试选举,等待的时间:

DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

   一定的延迟(500ms)确保我们等待FAIL状态在集群中传播,否则slave立即尝试选举(不进行等待的话),不过此时其他masters或许尚未意识到FAIL状态,可能会拒绝投票。
   随机延迟用于破坏同步slave,所以他们不太可能在同一时间开始选举。
   该SLAVE_RANK是slave从master复制过来的数据处理数量的排名。slave交换消息时,当master失败,以(尽力而为)建立一个排名:最新的复制数据偏移量slave排名0,第二最新排名1 ,依此类推。如果靠前排名的slave选举失败,其它很快会接着做。排名顺序没有严格执行;如果排名较高的slave落选,其他人会尝试很快。一旦,slave选举成功,它将获取一个新的、唯一的、自增的configEpoch值,此值比集群中任何masters持有的都要大,它开始宣称自己是master,并通过ping、pong数据包传播,并提供自己的新的configEpoch以及持有的slots列表。为了加快其他nodes的重新配置,pong数据包将会在集群中广播。当前node不可达的那些节点,它们可以从其他节点的ping或者pong中获知信息(gossip),并重新配置。
   其他节点也会检测到这个新的master和旧master持有相同的slots,且持有更高的configEpoch,此时也会更新自己的配置(epoch,以及master);旧master的slaves不仅仅更新配置信息,也会重新配置并与新的master跟进(slave of)。

Masters响应slave的投票请求

投票需要满足如下条件:

①此master只会对指定的epoch投票一次,并且拒绝对旧的epoch投票:每个master都持有一个lastVoteEpoch,将会拒绝AUTH_REQUEST中currentEpoch比lastVoteEpoch小的请求。当一个master节点积极响应一个投票请求,那么
lastVoteEpoch 会相应地进行更新,同时安全地存储到磁盘。

②一个master节点投票给某个slave节点只有当该slave节点的master节点被标记为FAIL。

③如果slave的currentEpoch比此master的currentEpoch小,那么AUTH_REQUEST将会被忽略,如果同一个slave再此请求投票,持有已经增加的currentEpoch,它(slave)将保证旧的投票响应不能参与计票。

比如master的currentEpoch为5,lastVoteEpoch为1:

①slave的currentEpoch为3

②slave在选举开始时,使用epoch为4(先自增),因为小于master的epoch,所以投票响应被延缓。

③slave在一段时间后将重新选举,使用epoch为5(4 + 1,再次自增),此时master上延缓的响应发给slave,接收后视为有效。

另外:

①master在2 * NODE_TIMEOUT超时之前,不会对同一个master的slave再次投票,它确保当一个slave选举成功后,它(slave)有一段缓冲时间来通知其他的slaves,以避免另一个slave节点发起另一个新的选举。

②master并不会尽力选举最合适的slave。最合适的slave应该在其他slaves之前,首先发起选举并胜出。

③当master拒绝一个slave投票,并不会发出一个“否决”响应,而是简单的忽略。

④请求投票的slave节点必须拥有它想要进行故障转移的哈希槽的配置信息,而且信息应该比它请求投票的master节点的配置信息更新或者一致。

⑹Hash Slots配置传播

Redis 集群很重要的一个部分是用来传播关于集群节点负责哪些哈希槽的信息的机制。

哈希槽配置传播有两种方法:

①heartbeat
消息:发送者的ping、pong消息中,总是携带自己目前持有的slots信息,不管自己是master还是slave。

②UPDATE消息。因为每一次心跳数据包中有一个关于发送者configEpoch信息和负责的哈希槽,如果心跳包的接收方发现发送者信息是过时的,它会发送新信息的数据包,迫使过时的节点更新其信息。

⑺ nodes如何重新加入集群

 node A被告知slot 1、2现在有node B接管,假如这两个slots目前有A持有,且A只持有这两个slots,那么此后A将放弃这2个slots,成为空的节点;此后A将会被重新配置,成为其他新master的slave。这个规则可能有些复杂,A离群一段时间后重新加入集群,此时A发现此前自己持有的slots已经被其他多个nodes接管,比如slot
1被B接管,slot 2被C接管。

在重新配置时,最终此节点上的slots将会被清空,那个窃取自己最后一个slot的node,将成为它的新master。节点重新加入集群,通常发生在failover之后,旧的master(也可以为slave)离群,然后重新加入集群。
⑻ Replica迁移

Redis
Cluster实现了一个成为“Replica migration”的概念,用来提升集群的可用性。比如集群中每个master都有一个slave,当集群中有一个master或者slave失效时,而不是master与它的slave同时失效,集群仍然可以继续提供服务。

①master A,有一个slave A1

②master A失效,A1被提升为master

③一段时间后,A1也失效了,那么此时集群中没有其他的slave可以接管服务,集群将不能继续服务。

如果masters与slaves之间的映射关系是固定的(fixed),提高集群抗灾能力的唯一方式,就是给每个master增加更多的slaves,不过这种方式开支很大,需要更多的redis实例。

解决这个问题的方案,我们可以将集群非对称,且在运行时可以动态调整master-slaves的布局(而不是固定master-slaves的映射),比如集群中有三个master
A、B、C,它们对应的slave为A1、B1、C1、C2,即C节点有2个slaves。“Replica迁移”可以自动的重新配置slave,将其迁移到某个没有slave的master下。

①A失效,A1被提升为master

②此时A1没有任何slave,但是C仍然有2个slave,此时C2被迁移到A1下,成为A1的slave

③此后某刻,A1失效,那么C2将被提升为master。集群可以继续提供服务。

⑼Replica迁移算法

这个算法确保最终每个master至少有一个slave即可,同时避免大规模迁移。触发时机为,检测出有一个master没有健康slave,。参与迁移的slave必须为,持有最多slaves的master的其中一个slave,且不处于FAIL状态,且持有最小的node
ID。

    比如有10个masters都持有一个slave,有2个masters各持有5个slaves,那么迁移将会发生在持有5个slaves的masters中,且node ID最小的slave node上。不过也有可能当集群的配置不够稳定时,有一种竞争情况的发生,即多个slaves都认为它们自己的ID最小;如果这种情况发生,结果就是可能多个slaves会迁移到同一个master下,不过这并没有什么害处,但是最坏的结果是导致原来的master迁出了所有的slaves,让自己变得单一。但是迁移算法(进程)会在迁移完毕之后重新判断,如果尚未平衡,那么将会重新迁移。

    最终,每个master最少持有一个slave;这个算法由用户配置的“cluster-migration-barrier”,此配置参数表示一个master至少保留多少个slaves,其他多余的slaves可以被迁出。此值通常为1,如果设置为2,表示一个master持有的slaves个数大于2时,多余的slaves才可以迁移到持有更少slaves的master下。
⑽configEpoch冲突解决算法

当持有不同slots的masters持有相同的configEpoch(概率极小)

①如果一个master节点发现其他master持有相同的configEpoch。

②并且此master逻辑上持有较小的node ID(字典顺序)

③然后此master将自己的currentEpoch加1,并作为自己新的configEpoch。
如果有多个nodes持有相同的congfigEpoch,多次重复上面的方式,使所有的master的configEpoch唯一。

⑾节点重置

所有的nodes都可以进行软件级的reset(不需要重启、重新部署它们),reset为了重用集群(重新设定集群),必须需要将某个(些)节点重置后添加到其他集群。我们可以使用“CLUSTER
RESET”指令:

①CLUSTER RESET SOFT

②CLUSTER RESET HARD

指令必须直接发给需要reset的节点,如果没有指定reset类型,默认为SOFT。

①soft和hard:如果节点为slave,那么节点将会转换为master,并清空其持有的数据,成为一个空的master。如果此节点为master,且持有slots数据,那么reset操作将被中断。

②soft和hard:其上持有的slots将会被释放

③soft和hard:此节点上的nodes映射表将会被清除,此后此node将不会知道其他节点的存在与状态。

④hard:currentEpoch、configEpoch、lastVoteEpoch值将被重置为0。

⑤hard:此nodeID将会重新生成。

持有数据的(slot映射不为空的)master不能被reset(除非现将此master上的slot手动迁移到其他nodes上,或者手动failover,将其切换成slave);在某些特定的场景下,在执行reset之前,或许需要执行FLUSHALL来清空原有的数据。

⑿ 集群中移除节点

实际中可能从一个集群中删除一个节点,把数据重新分布到其它节点(如果它是master),然后关掉它。然而,其它节点仍然记录了它的节点ID和地址,并且尝试重连.

因为这个原因,当删除一个节点,我们想把条目从所有其它节点列表中删除。这可以使用CLUSTER FORGET <node-id> 命令来实现,这个命令做两件事:

1.从节点列表中删除指定的节点ID.

2.在60s间隔内,阻止相同节点ID的节点重新加入.
因为FORGET指令将会通过gossip协议传播给其他nodes,集群中所有的节点都收到消息是需要一定的时间延迟。



 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: