《Redis开发与运维》笔记-集群概念

数据分布

数据分布理论

分布式数据首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个自己。
分布式存储数据分区
重点关注的就是数据分区规则。常见的分区规则有哈希分区和顺序分区两种,Redis Cluster则采用的哈希分区规则。哈希分区规则有几种:

  1. 节点取余分区
    使用特定的数据,如Redis的键或用户ID,在根据节点数量N使用公式:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。但是当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。

  2. 一致性哈希分区
    一致性哈希分区实现思路是为系统中每个节点分配一个token,范围一般在0~ 2 32 2^{32} 232,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于改哈希值的token节点。这种方式好处在于加入和删除节点只会影响哈希环中相邻的节点,对其他节点无影响。但是存在几个问题:

    • 加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
    • 当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此不适合少量数据节点的分布式方案。
    • 普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。
  3. 虚拟槽分区
    虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数量,比如Redis cluster槽范围是0~16383.槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。

Redis 数据分区

Redis cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式slot = CRC16(key)& 16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。

Redis虚拟槽分区的特点:

  • 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
  • 节点自身维护槽的映射关系,不需要客户端或者代理服务器维护槽分区元数据。
  • 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。

集群功能限制

  1. key批量操作支持有限。如mset、mget,目前只支持具有相同slot值的key执行批量操作。
  2. key事务操作支持有限。同理只支持多key在同一个节点上的事务操作,当多个key分布在不同的节点上时无法使用事务功能。
  3. key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash、list等映射到不同的节点。
  4. 不支持多数据空间。单机下的Redis可以支持16个数据库,集群模式下只能使用一个数据库空间,即db0.
  5. 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。

节点通信

通信流程

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息。常见的元数据维护方式分为:集中式和P2P方式。Redis集群采用P2P的Gossip协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播。
通信过程说明:

  1. 集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000。

注意TCP通道端口号,很多集群搭建过程中出现链接不上,很大部分原因就是通道端口没开放。

  1. 每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
  2. 接收到ping消息的节点用pong消息作为响应。

Gossip消息

Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息。
常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等。

  • meet消息:用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。
  • ping消息:集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。
  • pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。
  • fail消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。

节点选择

Redis集群内节点通信采用固定频率(定时任务每秒执行10次)。因此节点每次选择需要通信的节点列表变得非常重要。通信节点选择过多虽然可以做到信息及时交换但是成本过高。节点选择过少会降低集群内所有节点彼此信息交互频率,从而影响故障判定、新节点发现等需求的速度。因此Redis集群的Gossip协议需要兼顾信息交换实时性和陈本开销,通信节点选择的规则如图:
节点通信选择规则

根据通信节点选择的流程可以看出消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。

  1. 选择发送消息的节点数量
    集群内每个节点维护定时任务默认每秒执行10次,每秒会随机选取5个节点找出最久没有通信的节点发送ping消息,用于保证Gossip信息交换的随机性。每100毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于 cluster_node_timeout / 2,则立刻发送ping消息,防止该节点信息太长时间未更新。根据以上规则得出每个节点每秒需要发送ping消息的数量= 1 + 10 * num (node.pong_received > cluster_node_timeout / 2),因此cluster_node_timeout参数对消息发送的节点数量影响非常大。当带宽资源紧张时,可以适当调大这个参数。反之,过度调大时会影响消息交换的频率从而影响故障转移、槽信息更新、新节点发现的速度。
  2. 消息数据量
    每个ping消息的数据量体现在消息头和消息体中,其中消息头主要占用空间的字段是myslots[CLUSTER_SLOTS / 8],占用2KB,这块空间占用相对固定。消息体会携带一点数量的其他节点信息用于信息交换。

故障转移

故障发现

Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线和客观下线。

这个感觉是不是似曾相识?哨兵好像也是这么干的

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

  2. 客观下线
    当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。ping/pong消息的消息体会携带集群1/10的其他节点状态数据,当接受节点发现消息体中含有主观下线的节点状态时,会在本地找到故障节点的ClusterNode结构,保存到下线报告链表中。
    通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时,触发客观下线流程。

故障发现决策参与的必须是负责槽的主节点。
集群模式下只有处理槽的主节点才负责读写请求和集群槽等关键信息维护,从节点只进行主节点数据和状态信息的复制。
如果在cluster-node-time*2时间内无法收集到一半以上槽节点的下线报告,那么之前的下线报告将会过期,也就是说主观下线上报的速度追赶不上下线报告过期的速度,那么故障节点将永远无法被标记为客观下线从而导致故障转移失败。因此不建议将cluster-node-time设置得过小。

客观下线逻辑

尝试客观下线

故障恢复

故障恢复流程:
故障恢复流程

  1. 资格检查
    每个节点都要检查最后与主节点断线时间,判断是否具有资格替换故障的主节点。如果从节点与主节点断线时间超过cluster-node-time*cluster-slave-validity-factor,则当前从节点不具备故障转移资格。参数cluster-slave-validity-factor用于从节点的有效因子,默认为10。

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

  3. 发起选举
    当从节点定时任务检测到达故障选举时间到达后,发起选举流程:

    a. 更新配置纪元
    配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元标示当前主节点的版本,所有主节点的配置纪元都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全局的配置纪元,用于记录集群内所有主节点配置纪元的最大版本。
    配置纪元的主要作用:

     1. 标示集群内每个主节点的不同版本和当前集群最大的版本。
     2. 每次集群发生重要事件时,这里的重要事件指出现新的主节点,从节点竞争选举。都会递增集群全局的配置纪元并赋值给相关主节点,用于记录这一关键事件。
     3. 主节点具有更大的配置纪元代表了更新的集群状态,因此当节点间进行ping/pong消息交换时,如出现slots等关键信息不一致时,以配置纪元更大的一方为准,防止过时的消息状态污染集群。
    

    b. 广播选举信息
    在集群内广播选举消息,并记录已发送过消息的状态,保证该从节点在一个配置纪元内只能发起一次选举。

  4. 选举投票
    只有持有槽的主节点才会处理故障选举消息,因为每个持有槽的节点在一个配置纪元内部都有唯一的一张选票,当接到第一个请求投票的从节点消息时回复FAILOVER_AUTH_ACK消息作为投票,之后相同配置纪元内其他从节点的选举消息将忽略。
    投票作废:每个配置纪元代表了一次选举周期,如果在开始投票之后的cluster-node-timeout * 2时间内从节点没有获取足够数量的投票,则本次选举作废。从节点对配置纪元自增并发起下一轮投票,知道选举成功为止。

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

  1. 替换主节点
    当从节点收集到足够的选票之后,触发替换主节点操作:
    1). 当前从节点取消复制变为主节点。
    2). 执行clusterDelSlot操作撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽委派给自己。
    3). 向集群广播自己的pong消息,通知集群内所有的节点当前从节点变为主节点并接管了故障主节点的槽信息。

故障转移时间

  1. 主观下线(pfail)识别时间=cluster-node-timeout。
  2. 主观下线状态消息传播时间<=cluster-node-timeout/2。
  3. 从节点转移时间<=1000毫秒。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Layne_lei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值