redis集群规范

集群目标保证

Redis 集群是 Redis 的一个分布式实现,在设计中就需要考虑其服务可用性,稳定性,性能,安全等问题,这也是redis集群需要尽量实现的目标,官网中有以下几点说明。

  • 可扩展性:能够在1000节点时候仍然能够很好的进行扩展且是线性的
  • 无合并操作:合并操作涉及多个键值,而这些键可能处于不同的节点,计算效率差
  • 写入安全:保证数据没有写入丢失,主要在主从同步数据时可能出现丢失。
  • 可用性:在绝大多数的主节点是可达的,并且对于每一个不可达的主节点都至少有一个它的从节点可达的情况下,Redis 集群仍能进行分区操作。

redis集群实现了所有单机版本中出现的处理单key的命令,而基本没有实现多key的复杂操作,避免了大量的跨节点操作,保证性能。集群只有0号数据库,也没有select操作。

集群中的节点负责存储数据、记录集群的状态。并能通过节点之间的相互通信自动发现其他节点,检测出没正常工作的节点, 并且在需要的时候在从节点中推选出主节点。每个节点只会做自己节点上的数据相应,非本节点数据不会代理请求,而是返回客户端正确的节点地址由客户端再次访问,所以客户端缓存key的保存服务器可以大大的减少节点访问次数。

集群的拓扑结构

Redis 集群是一个网状结构,每个节点都通过 TCP 连接跟其他任意节点连接。

在一个有 N 个节点的集群中,每个节点都与 其他N-1 个节点建立了持续的 TCP 连接,且这个连接是双向的。 这些 TCP 连接会永久保持,并不是按需创建的。

节点连接  

节点总是在集群连接端口接受连接,必要时会回复接收到其他节点发来的 ping 包,即使有可能发送 ping 包的节点是不可信的。 然而如果某个节点不被认为是在集群中,那么所有它发出的所有数据包被其他节点收到后都会被丢弃掉而不做处理,节点只会对集群中其他节点的ping包做反应。

让一个redis实例成为一个集群的节点,需要受到集群认可,主要又两种方式。

  • 主动介绍自己:自己主动向自己已知节点发送meet请求,如果成功接受方则会认可该新节点为集群的一部分。meet命令只能由系统管理员使用CLUSTER MEET ip port 命令发送
  • 间接的介绍:通过步骤一使得一部分节点承认了新节点,已知的节点会向其他节点发送gossip消息,让集群中未收到meet消息的节点承认新节点的为集群的一部分,从而使得所有节点相互之间建立稳定的双向的TCP连接。

MOVED和ASK请求

集群的增加或者减少一个节点都将会造成节点上hash槽进行移动,其本质也是hash槽的从新分配。完成该工作可以使用以下 CLUSTER 的子命令。

  • CLUSTER ADDSLOTS slot1 [slot2] … [slotN]
  • CLUSTER DELSLOTS slot1 [slot2] … [slotN]
  • CLUSTER SETSLOT slot NODE node
  • CLUSTER SETSLOT slot MIGRATING node
  • CLUSTER SETSLOT slot IMPORTING node

setslot有三种形式,NODE,MIGGATING,IMPORTING。使用

  • NONE:向指定id的节点指派hash槽,哈希槽被指派后,节点会将这个消息通过gossip协议向整个集群传播
  • MIGRATING:从节点转出hash槽,在转出过程中被访问到该hash槽,该节点还存在则直接回应,如果已经转出不存在,则向转出的节点发送- ASK请求重定向到转出的目标节点。
  • IMPORTING:向直指定节点转入hash槽,转入后只有接受到客户端ASKING命令才会处理该槽。如果没有收到客户端的ASKING,则向原来的节点发送-MOVED重定向来处理该key。

节点的失效检测

当一个节点无法被集群中大部分的节点访问时,该节点可能已经失效,如果时主节点,需要从它的从节点中提升一个节点作为主节点继续保证节点的写入能力。若如果无法提升从节点来做主节点的话,那么整个集群就置为错误状态并停止接收客户端的查询。

PFAIL标识

PFAIL 表示可能失效(Possible failure)的状态,这是一个非公认的(non acknowledged)失效类型。

一个节点在超过 NODE_TIMEOUT 时间后仍无法访问某个节点,那么它会用 PFAIL 来标识这个不可达的节点,任意节点都可标识。当节点A主观标识某个节点B的状态为PFAIL后,节点A会从其他节点发送的心跳包中查看节点B的状态(每个节点向其他每个节点发送的 gossip 消息中有包含一些随机的已知节点的状态),如果节点A通过 gossip 字段收集到集群中大部分主节点标识的节点 B 的状态信息为PFAIL 状态,或者在 NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT 这个时间内是处于 PFAIL 状态,节点A标记节点B的状态将会从PFAIL转变为FAIL。

FAIL标识

被标记为FAIL状态的节点基本可以确定为已经失效。节点A标记节点B失效后,将会利用gossip消息向集群中的节点传播节点B为FAIL状态的消息,接受到该消息的节点会将让自己将节点B标记为FAIL,最终大部分节点将会标记节点B为FAIL状态,如果节点B为主节点,将会进行从节点的节点提升,将其从节点提升为主节点以代替B节点的工作。

FAIL 标识基本都是单向的。一个节点能从 PFAIL状态升级到 FAIL 状态,但要清除 FAIL 标识只有以下两种可能方法:

  • 该节点已经恢复可达的,并且它是一个从节点。在这种情况下,FAIL 标识可以清除掉,因为从节点不会进行故障转移。
  • 该节点已经恢复可达的,但是它是一个主节点。想要取消FAIL就需要保证在这之前该节点的主节点位置没有被他的从节点取代,也就是经过了很长时间(N * NODE_TIMEOUT)后也没有检测到任何从节点被提升。

本质上来说,FAIL 标识只是用来触发从节点提升(slave promotion)算法的安全部分。理论上一个从节点会在它的主节点不可达的时候独立起作用并且启动从节点提升程序,然后等待主节点来拒绝认可该提升(如果主节点对大部分节点恢复连接)。

集群纪元

currentEpoch

currentEpoch是用来记录整个集群共有的版本号信息,当有多个节点提供了冲突的信息的时候,另外的节点就可以通过这个状态值来了解哪个是最新的,并同步更新自己currentEpoch为最新值。

currentEpoch是一个 64bit 的 unsigned数,节点初始currentEpoch值为0,当节点接收到来自其他节点的 ping 包或 pong 包的时候,如果其他节点的消息中的epoch比自身epoch大,当前节点将会更新自己的currentEpoch,这样可以使得整个集群currentEpoch值是相同的。

configEpoch

这个变量是记录节点自身的特征值, 主要用于判断槽的归属节点是否需要更新。当一个从节点由于其归属的主节点下线而被提升为新的主节点后,他会更新自己的configEpoch信息,确保它的版本号是最大的,并向其他的节点发送这个信息。如果原来的主节点故障恢复,但是还没有收到自己主节点位置被取代的消息,任然向其他节点以主节点身份进行活动,但是其configEpoch值已经不是最新的,所以不会被其他节点认可,最终这个节点也会收到来自新主节点发送的消息,知道自己已被取代,而作为一个从节点从属于新的主节点。

丛节点的选举和提升

从节点的选举和提升都是由从节点处理的,主节点会投票要提升哪个从节点。一个从节点发起选举必须时主节点已经被下线,也就是主节点需要被至少一个具有成为主节点必备条件的从节点标记为 FAIL 的状态时。

从节点发起选举的条件

  • 该从节点的主节点处于 FAIL 状态。
  • 它的原主节点负责的哈希槽数目不为零。
  • 它和原主节点之间的重复连接(replication link)断线不超过一段给定的时间,这是为了确保该从节点的数据是可靠的。
  • 一个从节点想要被推选出来,那么第一步应该是提高它的 currentEpoch 计数,并且向主节点们请求投票。

从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一点点延迟。这段延时主要

  • 保证主节点FAIL的消息被其他主节点知道,否则从节点发起投票后其他主节点并不知道原主节点下线。
  • 让从节点有时间获取与主节点断线期间的最新数据。
  • 通过加入一个随机时间减少多个节点同时发送选举请求的可能性,

从节点提升流程

  • 从节点通过广播一个 FAILOVER_AUTH_REQUEST 数据包给集群里的每个主节点来请求选票。然后等待回复(最多等 NODE_TIMEOUT 这么长时间)。
  • 一旦一个主节点给这个从节点投票,会回复一个 FAILOVER_AUTH_ACK,并且在 NODE_TIMEOUT * 2 这段时间内不能再给同个主节点的其他从节点投票。确保一个节点只能投一票。
  • 被投票的从节点会忽视所有带有的时期(epoch)参数比 currentEpoch 小的回应(ACKs),这样能避免把之前的投票的算为当前的合理投票。
  • 一旦某个从节点收到了大多数主节点的回应,那么它就赢得了选举。否则,如果无法在 NODE_TIMEOUT 时间内访问到大多数主节点,那么当前选举会被中断并在 NODE_TIMEOUT * 4 这段时间后由另一个从节点尝试发起选举。

一旦有从节点赢得选举,它就会开始用 ping 和 pong 数据包向其他节点宣布自己是主节点,并提供它负责的哈希槽,设置 configEpoch 为 currentEpoch(选举开始时生成的)。并为了加速其他节点的重新配置,该节点会广播一个 pong 包 给集群里的所有节点,其他节点会检测到有一个新的主节点在负责处理之前一个旧的主节点负责的哈希槽,就会升级自己的配置信息。当原主节点再次上线或者新的从节点上线,都会作为这个主节点的从节点。

主节点投票

主节点接收到来自于从节点、要求以 FAILOVER_AUTH_REQUEST 请求的形式投票的请求,主节点想要发起投票,需要满足以下的条件。

  • 主节点只会在一个epoch时段投票一次,并且拒绝以前时段的投票:lastVoteEpoch记录着最新的一次投票的信息,当该主节点接受投票后,该lastVoteEpoch值将会更新,如果收到的投票请求的currentEpoch比lastVoteEpoch中的值小,说明已经进行过投票或者请求已经过期,直接忽略。
  • 主节点投票必须认为发起投票的主节点的状态为FAIL。
  • 主节点的回应总是带着和投票请求一致的currentEpoch。
  • 主节点如果拒绝为一个从节点进行投票,他只是忽略掉这个消息即可。不会给任何负面的回应。
  • 主节点会查看发起投票的从节点的configEpoch值,该值必须比它的原主节的更大,以保证该节点的数据时足够的新,否则不会给予投票。这个configEpoch值会在请求信息中携带,同时还会附带slots信息。

备份迁移

在集群中有主节点-从节点的设定,如果主从节点间的映射关系是固定的,那么久而久之,当同一组主从映射中多个节点独立故障的时,有可能造成这一主从映射中全部节点下线,集群停止服务,这样系统可用性会变得很有限。redis为了保证每个主节点拥有的从节点数量相对较均衡,当一个主从映射中节点连续下线后,为了保证该主从映射中保存的slots能够稳定的访问,会将集群其他主节点的从节点重新分配到这个从节点相对缺少主节点上。

具体的过程是:

集群中任意一个节点检测到没有一个好的从节点(好的从节点:该节点对目标节点的主观状态不为FAIL)的主节点,将会出发从节点的迁移工作。迁移总是从最多从节点的主节点得从节点中选取, 且从节点不处于FAIL状态且拥有最小得节点ID。

在集群配置信息不稳定的情况下,有可能发生一种竞争情况:多个从节点都认为自己是不处于 FAIL 状态并且拥有较小节点 ID(实际上这是一种比较难出现的状况)。如果这种情况发生的话,结果是多个从节点都会迁移到同个主节点下,不过这种结局是无害的。这种竞争有时候会使得割让出从节点的主节点变成没有任何备份节点,当集群再次达到稳定状态的时候,本算法会再次执行,然后把从节点迁移回它原来的主节点。最终每个主节点都会至少有一个从节点作为备份节点。通常表现出来的行为是,一个从节点从一个拥有多个从节点的主节点迁移到一个孤立的主节点。

这个行为迁移行为可以通过一个用户可配置的参数 cluster-migration-barrier 进行控制。这个参数表示的是,一个主节点在拥有多少个好的从节点的时候就要割让一个从节点出来

原文地址:https://www.cnblogs.com/k5210202/p/13080635.html