redis进阶之集群

Author Avatar
Sean Yu 12月 24, 2020
  • 在其它设备中阅读本文章

集群

Redis Cluster是Redis的分布式解决方案,在3.0版本正式推出,有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构方案达到负载均衡的目的。

数据分布

数据分布理论

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。需要重点关注的是数据分区规则。常见的分区规则有哈希分区和顺序分区两种

DRFpzF.png

DRFkZR.png

由于Redis Cluster采用哈希分区规则,这里我们重点讨论哈希分区,常见的哈希分区规则有几种

扩展知识: https://segmentfault.com/a/1190000021199728

节点取余分区

使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式: hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。这种方案存在一个问题:
当节点数量变化时,如扩容或收缩节点,数据节点映射关 系需要重新计算,会导致数据的重新迁移。
这种方式的突出优点是简单性,常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区数,比如划分为512或1024张表,保证可支撑未来一段时间的数据量,再根据负载情况将表迁移到其他数据库中。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况

DRF6YV.png

一致性哈希分区

一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个token,范围一般在0~232,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点

DRFTFx.png

这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。但一致性哈希分区存在几个问题:

  • 加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
  • 当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此 这种方式不适合少量数据节点的分布式方案。
  • 普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。
虚拟槽分区

虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数, 把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如Redis Cluster槽范围是0~16383。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽,如图10-4所示。

DRAjII.png

当前集群有5个节点,每个节点平均大约负责3276个槽。由于采用高质量的哈希算法,每个槽所映射的数据通常比较均匀,将数据平均划分到5个节点进行数据分区。Redis Cluster就是采用虚拟槽分区,下面就介绍Redis数据分区方法。

Redis数据分区

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

DRZqLF.png

Redis虚拟槽分区的特点:

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

集群功能限制

Redis集群相对单机在功能上存在一些限制,需要开发人员提前了解,
在使用时做好规避。限制如下:

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

搭建集群

准备节点

Redis集群一般由多个节点组成,节点数量至少为6个才能保证组成完整 高可用的集群。每个节点需要开启配置cluster-enabled yes,让Redis运行在集 群模式下。建议为集群内所有节点统一目录,一般划分三个目录:conf、 data、log,分别存放配置、数据和日志相关文件。把6个节点配置统一放在conf目录下,集群相关配置如下:

#节点端口
port 6379
# 开启集群模式
cluster-enabled yes
# 节点超时时间,单位毫秒
cluster-node-timeout 15000
# 集群内部配置文件
cluster-config-file "nodes-6379.conf"

启动过程如图10-6所示。

DRmkcV.png

节点握手

节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信, 达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命令:cluster meet{ip}{port}

DRncM6.png

  • 节点6379本地创建6380节点信息对象,并发送meet消息。
  • 节点6380接受到meet消息后,保存6379节点信息并回复pong消息。
  • 之后节点6379和6380彼此定期通过ping/pong消息进行正常的节点通信。

DRnoRI.png

分配槽

Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固 定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过 cluster addslots命令为节点分配槽。

假如我们有六个节点,三个节点加入集群进行槽分配,还有三个节点没有使用,作为一个完整的集群,每个负责处理槽的 节点应该具有从节点,保证当它出现故障时可以自动进行故障转移。

集群模 式下,Reids节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。使用cluster replicate{nodeId}命令让一个节点成为从节点。其中命令执行必须在对应的 从节点上执行,nodeId是要复制主节点的节点ID。

Redis集群模式下的主从复制使用了之前介绍的Redis复制流程,依然支 持全量和部分复制。复制(replication)完成后,整个集群的结构如图10-11 所示。

DRQ2pd.png

节点通信

通信流程

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指: 节点负责哪些数据,是否出现故障等状态信息。

常见的元数据维护方式分为: 集中式和P2P方式。Redis集群采用P2P的Gossip(流言)协议, Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播,如图10-12所示。

DRQvn0.png

通信过程说明:

  • 1) 集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000。
  • 2) 每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
  • 3) 接收到ping消息的节点用pong消息作为响应。

集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全 部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它 们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息 变更等事件发生时,通过不断的ping/pong消息通信,经过一段时间后所有的 节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。

Gossip消息

常用的Gossip消息可分为:

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

节点选择

虽然Gossip协议的信息交换机制具有天然的分布式特性,但它是有成本 的。由于内部需要频繁地进行节点信息交换,而ping/pong消息会携带当前节 点和部分其他节点的状态数据,势必会加重带宽和计算的负担。Redis集群 内节点通信采用固定频率(定时任务每秒执行10次)。

因此节点每次选择需 要通信的节点列表变得非常重要。通信节点选择过多虽然可以做到信息及时 交换但成本过高。节点选择过少会降低集群内所有节点彼此信息交换频率, 从而影响故障判定、新节点发现等需求的速度。

因此Redis集群的Gossip协议需要兼顾信息交换实时性和成本开销。

DR38Tf.png

选择发送消息的节点数量

集群内每个节点维护定时任务默认每秒执行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参数对消息发送的节点数量影响非常大。当我们的带宽 资源紧张时,可以适当调大这个参数.

消息数据量

每个ping消息的数据量体现在消息头和消息体中,其中消息头主要占用空间的字段是myslots[CLUSTER_SLOTS/8],占用2KB,这块空间占用相对固定, 是节点自身信息。消息体会携带一定数量的其他节点信息用于信息交换。

集群伸缩

Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务 的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容

其中原 理可抽象为槽和对应数据在不同节点之间灵活移动。首先来看我们之前搭建的集群槽和数据与节点的对应关系

DRJxBR.png

三个主节点分别维护自己负责的槽和对应的数据,如果希望加入1个节 点实现集群扩容时,需要通过相关命令把一部分槽和数据迁移给新节点,如 图10-18所示。

DRYVud.png

图中每个节点把一部分槽和数据迁移到新的节点6385,每个节点负责的 槽和数据相比之前变少了从而达到了集群扩容的目的。这里我们故意忽略了 槽和数据在节点之间迁移的细节,目的是想让读者重点关注在上层槽和节点 分配上来,理解集群的水平伸缩的上层原理: 集群伸缩=槽和数据在节点之间的移动,下面将介绍集群扩容和收缩的细节。

扩容集群

准备两个新节点6385和6386,新节点依然采用cluster meet命令加入到现有集群中。在集群内任意节点
执行cluster meet命令让6385和6386节点加入进来。集群内新旧节点经过一段时间的ping/pong消息通信之后,所有节点会发现新节点并将它们的状态保存到本地。

新节点刚开始都是主节点状态,但是由于没有负责的槽,所以不能接受 任何读写操作。对于新节点的后续操作我们一般有两种选择:

  • 为它迁移槽和数据实现扩容。
  • 作为其他主节点的从节点负责故障转移。
迁移槽和数据

槽迁移计划

槽是Redis集群管理数据的基本单位,首先需要为新节点制定槽的迁移 计划,确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节 点负责相似数量的槽,从而保证各节点的数据均匀。例如,在集群中加入 6385节点,如图10-21所示。加入6385节点后,原有节点负责的槽数量从 6380变为4096个。

DRtNsH.png

迁移数据

数据迁移过程是逐个槽进行的,每个槽数据迁移的流程如图10-23所示。

  • 对目标节点发送cluster setslot{slot}importing{sourceNodeId}命令,让目标节点准备导入槽的数据。
  • 对源节点发送cluster setslot{slot}migrating{targetNodeId}命令,让源节点准备迁出槽的数据。
  • 源节点循环执行cluster getkeysinslot{slot}{count}命令,获取count个属于槽{slot}的键。
  • 在源节点上执行migrate{targetIp}{targetPort}""0{timeout}keys{keys...}命令,把获取的键通过流水线(pipeline)机制批量迁移到目标节点,批量 迁移版本的migrate命令在Redis3.0.6以上版本提供,之前的migrate命令只能 单个键迁移。对于大量key的场景,批量键迁移将极大降低节点之间网络IO 次数。
  • 重复执行步骤3)和步骤4)直到槽下所有的键值数据迁移到目标节点。
  • 向集群内所有主节点发送cluster setslot{slot}node{targetNodeId}命令,通知槽分配给目标节点。为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽指向新节点。

DRtIYV.png

这里有个问题,redis每个节点的槽都是连续的么?

在集群初始建立的时候,同一节点上的槽是连续的,但扩容时,新节点会取所有其他节点上连续的槽为其分配,因此就不是连续的了。如下图:

初始状态:
DRaUL4.png

扩容后:
DRarJx.png

这也印证了槽分区与一致性哈希的不同,一致性哈希因为是连续的环,所以可以只迁移相邻的节点上的数据,但会造成分配不均。槽分区不会分配不均,但迁移时还是要动所有的旧节点的,只不过旧节点之间不用彼此迁移了(传统的哈希就需要)。

添加从节点

使用cluster replicate{masterNodeId}命令为主节点添加对应从节点,注意在集群模式下slaveof添加从节点操作不再支持。

从节点内部除了对主节点发起全量复制之外,还需要更新本地节点的集群相关状态.

收缩集群

下线迁移槽

收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点

  • 1) 首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
  • 2) 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其 他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。

DRaofP.png

下线节点需要把自己负责的槽迁移到其他节点,原理与之前节点扩容的迁移槽过程一致。源节点需要把自身负责的槽均匀地迁移到其他主节点上。

节点下限后

DRdet1.png

忘记节点

由于集群内的节点不停地通过Gossip消息彼此交换节点状态,因此需要 通过一种健壮的机制让集群内所有节点忘记下线的节点。也就是说让其他节 点不再与要下线节点进行Gossip消息交换。

Redis提供了cluster forget{downNodeId}命令实现该功能

当节点接收到cluster forget{down NodeId}命令后,会把nodeId指定的节 点加入到禁用列表中,在禁用列表内的节点不再发送Gossip消息。禁用列表 有效期是60秒,超过60秒节点会再次参与消息交换。也就是说当第一次 forget命令发出后,我们有60秒的时间让集群内的所有节点忘记下线节点。

线上操作不建议直接使用cluster forget命令下线节点,需要跟大量节点 命令交互,实际操作起来过于繁琐并且容易遗漏forget节点。建议使用redis- trib.rb del-node{host:port}{downNodeId}命令.

DRwQCq.png

客户端

Redis集群对客户端通信协议做了比较大的修改, 为了追求性能最大化,并没有采用代理的方式而是采用客户端直连节点的方式。因此对于希望从单机切换到集群环境的应用需要修改客户端代码。本节 我们关注集群请求路由的细节,以及客户端如何高效地操作集群。

请求重定向

在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再 根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复 MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重 定向,通知客户端请求正确的节点。

DRwgVH.png

重定向信息包含了键所对应的槽以及负责该槽的节点地址,根据这些信 息客户端就可以向正确的节点发起请求。节点对于不属于它的键命令只回复重定向响应,并不负责转发。

键命令

计算槽

Redis首先需要计算键所对应的槽。根据键的有效部分使用CRC16函数 计算出散列值,再取对16383的余数,使每个键都可以映射到0~16383槽范围内。

槽节点查找

Redis计算得到键对应的槽后,需要查找槽所对应的节点。集群内通过消息交换每个节点都会知道所有节点的槽信息,内部保存在clusterState结构中.

根据MOVED重定向机制,客户端可 以随机连接集群内任一Redis获取键所在节点,这种客户端又叫Dummy(傀儡)客户端,它优点是代码实现简单,对客户端协议影响较小,只需要根据 重定向信息再次发送请求即可。但是它的弊端很明显,每次执行键命令前都 要到Redis上进行重定向才能找到要执行命令的节点,额外增加了IO开销, 这不是Redis集群高效的使用方式。正因为如此通常集群客户端都采用另一 种实现:Smart(智能)客户端。

Smart客户端

大多数开发语言的Redis客户端都采用Smart客户端支持集群协议,Smart客户端通过在内部维护slot→node的映射关系,本地就可实现键到 节点的查找,从而保证IO效率的最大化,而MOVED重定向负责协助Smart客 户端更新slot→node映射。

  • 客户端初始化时会选择一个运行节点,初始化槽和节点映射关系
  • 客户端解析cluster slots结果缓存在本地,并为每个节点创建唯一的连接池
  • 执行命令
    • 计算slot并根据slots缓存获取目标节点连接,发送命令。
    • 如果出现连接错误,使用随机连接重新执行键命令。
    • 捕获到MOVED重定向错误,使用cluster slots命令更新slots缓存
    • 重复执行1)~3)步,直到命令执行成功

ASK重定向

Redis集群支持在线迁移槽(slot)和数据来完成水平伸缩,当slot对应 的数据从源节点到目标节点迁移过程中,客户端需要做到智能识别,保证键命令可正常执行。

例如当一个slot数据从源节点迁移到目标节点时,期间可 能出现一部分数据在源节点,而另一部分在目标节点

当出现上述情况时,客户端键命令执行流程将发生变化,如下所示:

  • 客户端根据本地slots缓存发送命令到源节点,如果存在键对象则直 接执行并返回结果给客户端。
  • 如果键对象不存在,则可能存在于目标节点,这时源节点会回复 ASK重定向异常。格式如下:(error)ASK{slot}{targetIP}:{targetPort}
  • 客户端从ASK重定向异常提取出目标节点信息,发送asking命令到目标节点打开客户端连接标识,再执行键命令。如果存在则执行,不存在则返 回不存在信息。

ASK与MOVED虽然都是对客户端的重定向控制,但是有着本质区别。 ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移 完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是 MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新 slots缓存。

为了支持ASK重定向,源节点和目标节点在内部的clusterState结构中维护当前正在迁移的槽信息,用于识别槽迁移情况,节点每次接收到键命令时,都会根据clusterState内的迁移属性进行命令处理.

故障转移

故障发现

主观下线

指某个节点认为另一个节点不可用,即下线状态,这个状 态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。

  • 节点a发送ping消息给节点b,如果通信正常将接收到pong消息,节点a更新最近一次与节点b的通信时间。
  • 如果节点a与节点b通信出现问题则断开连接,下次会进行重连。如果一直通信失败,则节点a记录的与节点b最后通信时间将无法更新。
  • 节点a内的定时任务检测到与节点b最后通信时间超高cluster-node-timeout时,更新本地对节点b的状态为主观下线(pfail)。
    DRDLIH.png
客观下线

指标记一个节点真正的下线,集群内多个节点都认为该节 点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节 点进行故障转移。

当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在 集群内传播。ping/pong消息的消息体会携带集群1/10的其他节点状态数据, 当接受节点发现消息体中含有主观下线的节点状态时,会在本地找到故障节 点的ClusterNode结构,保存到下线报告链表中。

通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。

这里有两个问题:

  • 为什么必须是负责槽的主节点参与故障发现决策?因为集群模式下 只有处理槽的主节点才负责读写请求和集群槽等关键信息维护,而从节点只 进行主节点数据和状态信息的复制。
  • 为什么半数以上处理槽的主节点?必须半数以上是为了应对网络分 区等原因造成的集群分割情况,被分割的小集群因为无法完成从主观下线到 客观下线这一关键过程,从而防止小集群完成故障转移之后继续对外提供服务(类似raft算法)

DRrUOK.png

  • 首先统计有效的下线报告数量,如果小于集群内持有槽的主节点总数的一半则退出。
  • 当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。
  • 向集群广播一条fail消息,通知所有的节点将故障节点标记为客观下线,fail消息的消息体只包含故障节点的ID。

故障恢复

故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它 的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从 节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节 点进入客观下线时,将会触发故障恢复流程。

DRsC11.png

资格检查

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

准备选举时间

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

发起选举

当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程如下:

  • 更新配置纪元: 配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元 (clusterNode.configEpoch)标示当前主节点的版本,所有主节点的配置纪元 都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全局的配 置纪元(clusterState.current Epoch)(类似raft算法中的任期)。配置纪元的应用场景有: ·新节点加入。 ·槽节点映射冲突检测。 ·从节点投票选举冲突检测。
  • 广播选举消息: 在集群内广播选举消息(FAILOVER_AUTH_REQUEST),并记录已发 送过消息的状态,保证该从节点在一个配置纪元内只能发起一次选举。
选举投票

跟raft差不多。

如集群内有N个持有槽的主节 点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个 从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。下线的主节点不计入投票。

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

替换主节点

当从节点收集到足够的选票之后,触发替换主节点操作:

  • 1) 当前从节点取消复制变为主节点。
  • 2) 执行clusterDelSlot操作撤销故障主节点负责的槽,并执行 clusterAddSlot把这些槽委派给自己。
  • 3) 向集群广播自己的pong消息,通知集群内所有的节点当前从节点变为主节点并接管了故障主节点的槽信息

集群中的问题调优

集群完整性

为了保证集群完整性,默认情况下当集群16384个槽任何一个没有指派 到节点时整个集群不可用。这是对集群完整性的一种保护措施,保证所有的 槽都指派给在线的节点。但是当持有槽的主节点下线时,从故障发现到自动 完成转移期间整个集群是不可用状态,对于大多数业务无法容忍这种情况, 因此建议将参数cluster-require-full-coverage配置为no,当主节点故障时只影 响它负责槽的相关命令执行,不会影响其他主节点的可用性。

带宽消耗

集群内Gossip消息通信本身会消耗带宽,官方建议集群最大规模在1000 以内,也是出于对消息通信成本的考虑,因此单集群不适合部署超大规模的节点。集群内所有节点通过ping/pong消息彼此 交换信息,节点间消息通信对带宽的消耗体现在以下几个方面:

  • 消息发送频率: 跟cluster-node-timeout密切相关,当节点发现与其他节 点最后通信时间超过cluster-node-timeout/2时会直接发送ping消息。
  • 消息数据量: 每个消息主要的数据占用包含:slots槽数组(2KB空 间)和整个集群1/10的状态数据(10个节点状态数据约1KB)。
  • 节点部署的机器规模:机器带宽的上线是固定的,因此相同规模的集 群分布的机器越多每台机器划分的节点越均匀,则集群内整体的可用带宽越 高。

优化:

  • 在满足业务需要的情况下尽量避免大集群。
  • 适度提高cluster-node-timeout降低消息发送频率,同时cluster-node- timeout还影响故障转移的速度,因此需要根据自身业务场景兼顾二者的平 衡。
  • 如果条件允许集群尽量均匀部署在更多机器上。避免集中部署

Pub/Sub广播问题

集群模式下内部实现对所有的publish命令都会向 所有的节点进行广播,造成每条publish数据都会在集群内所有节点传播一 次,加重带宽负担

image.png

当频繁应用 Pub/Sub功能时应该避免在大量节点的集群内使用,否则会严重消耗集群内 网络带宽。针对这种情况建议使用sentinel结构专门用于Pub/Sub功能,从而 规避这一问题。

读写分离

不建议集群模式下做读写分离(指的是单个分区不做读写分离,从节点只做故障转移功能)

集群倾斜

合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整体读取。避免映射到同一槽。