在上一篇文章中已经介绍了Redis的几种高可用技术:持久化、主从复制和sentry,但是这些方案仍然存在不足,其中最重要的是存储容量受限于单机,无法实现写操作的负载均衡。
本文将详细介绍集群,主要内容包括:
集群的作用
的集群构建方法和设计方案
聚类的基本原理
客户端访问集群的方法
实践笔记(集群扩展、故障转移、参数优化等。)
集群的作用
集群,即Redis集群,是Redis 3.0推出的分布式存储方案。集群由多个节点组成,Redis的数据分布在这些节点上。
集群中的节点分为主节点和从节点:只有主节点负责读写请求和维护集群信息;从节点只复制主节点的数据和状态信息。
集群的作用可以概括为两点:
数据分区
数据分区(或数据切片)是集群的核心功能。将集群数据分发到多个节点:
一方面突破了Redis单机内存大小的限制,存储容量大大增加。
另一方面,每个主节点可以对外提供读写服务,大大提高了集群的响应能力。
Redis单机的内存大小有限,在持久化和主从复制的介绍中有提到。
例如,如果单机内存过大,bgsave和bgrewriteaof的fork操作可能会导致主进程阻塞,当主机在主从环境中切换时,从节点可能会长时间无法提供服务,主节点的复制缓冲区可能会在完全复制阶段溢出。
高可用性
集群支持主节点的主从复制和自动故障转移(类似于sentry)。当任何节点出现故障时,群集仍然可以提供外部服务。本文基于Redis 3.0.6。
构建集群
我们将构建一个简单的集群:总共6个节点,3个主节点和3个从节点。为了方便起见,所有节点都在同一个服务器上,通过端口号来区分,配置也很简单。
3个主节点的端口号:7000/7001/7002;对应的从节点端口号:8000/8001/8002。
构建集群有两种方式:
手动执行Redis命令,逐步完成构造。
使用Ruby脚本构建
两者的原理是一样的,只是Ruby脚本打包了Redis命令。在实际应用中推荐使用脚本,简单快捷无错。下面介绍这两种方法。
执行Redis命令来构建集群。
集群构建可以分为四个步骤:
启动节点:以集群模式启动节点,此时节点是独立的,没有连接。
握手:让独立的节点连接成一个网络。
分配插槽:给主节点分配16384个插槽。
指定主从关系:为从节点指定主节点。
实际上,前三步完成后,集群就可以对外提供服务了;然而,在指定了从属节点之后,集群可以提供真正高可用性的服务。
开始节点
仍然可以使用redis-server命令启动该节点,但是需要以集群模式启动。
以下是7000个节点的配置文件(只列出了节点正常运行的关键配置,其他配置如开启AOF可以参考单机节点):
# redis-7000 . confport 7000 cluster-enabledyescluster-config-file node-7000 . conf 日志文件log-7000 . log dbfilename 转储-7000 . RDB 达蒙尼泽耶斯
其中,集群启用和集群配置文件是与集群相关的配置。
Cluster-enabledyes:Redis: Redis实例可以分为独立模式和集群模式;支持群集的染料可以启动群集模式。
对于以独立模式启动的Redis实例,如果执行info server命令,可以发现redis_mode是独立的,如下图所示:
对于集群模式的节点,其redis_mode为cluster,如下图所示:
Cluster-config-file:该参数指定集群配置文件的位置。每个节点将在运行期间维护一个群集配置文件。
每当集群信息发生变化时(例如添加或删除节点),集群中的所有节点都会将最新信息更新到该配置文件中。
当节点重新启动时,它将重新读取配置文件,获取集群信息,并可以轻松地重新加入集群。
也就是说,Redis节点在集群模式下启动时,会先寻找是否有集群配置文件。
如果是,从文件中的配置开始;如果没有,请初始化配置并将其保存到文件中。集群配置文件由Redis节点维护,不需要手动修改。
编辑配置文件后,使用redis-server命令启动节点:
redis-serverredis-7000.conf
节点启动后,可以通过cluster nodes命令查看节点的状态,如下图所示:
返回值的第一项表示节点id,由40个十六进制字符串组成。节点id不同于主从复制文章中提到的runId。
Redis每次启动都会重新创建runId,但是节点Id只在集群初始化时创建一次,然后保存在集群配置文件中,稍后节点重启时会直接在集群配置文件中读取。
其他节点启动方式相同,此处不再赘述。需要特别注意的是,在节点启动阶段,节点没有主从关系,所以从节点不会不需要添加slaveof配置。
节点握手
启动后,节点相互独立,不知道其他节点的存在;需要和节点握手,形成独立节点的网络。
节点间的握手通过集群meet {ip} {port}命令实现。例如,如果在7000个节点中执行clustermeet 192.168.72.128 7001,则可以完成7000个节点与7001个节点之间的握手。
注意:ip使用LAN ip,而不是localhost或127.0.0.1,这样其他机器上的节点或客户端也可以访问它。
此时,使用集群节点可以查看:
您也可以在7001节点下类似地查看它:
类似地,在7000个节点中使用cluster meet命令,可以将所有节点添加到集群中以完成握手:
192 . 168 . 72 . 7001集群会议192 . 168 . 72 . 1288001集群会议194
执行上述命令后,您可以看到7000个节点已经检测到所有其他节点:
通过节点之间的通信,每个节点都可以知道所有其他节点。以8000个节点为例:
分配槽
在Redis集群中,数据分区是通过插槽的方式实现的,具体原理后面会介绍。集群有16384个插槽,是数据管理和迁移的基本单位。
当数据库中的所有16384个插槽都被分配了节点时,集群就联机了(OK);如果没有为任何插槽分配节点,则群集处于离线状态(失败)。
Cluster info命令可以查看集群状态,以及分配槽失败前的状态:
使用cluster addslots命令分配插槽,并执行以下命令分配所有插槽(编号0-16383):
redis-CLI-p 7000 clusteraddslots { 0.5461 } redis-CLI-p 7001集群添加插槽{5462.10922 } redis-CLI-p 7002集群添加插槽{10923.16383}
此时,检查群集状态,显示所有插槽都已分配,群集处于在线状态:
指定主从关系
集群中指定的主从关系不再使用slaveof命令,而是使用cluster replicate命令;使用参数节点id。
通过集群节点获得几个主节点的节点id后,执行以下命令为每个从节点指定主节点:
redis-CLI-p 8000 clusterreplicatebe 816 EBA 968 BC 16 c 884 b 963d 768 c 945 e 86 AC 51 aere dis-CLI-p 8001 cluster replicate 788 b 361563 ABC 175 ce 8232569347812 a 12 f1 FD B4 redis-CLI-p 8002 cluster replicate 26 f 1624 a 3 e 5197 DDE 267 de 683d 61 bb 2 CBD
此时执行cluster nodes检查各个节点的状态,可以看到主从关系已经建立:
至此,集群构建完成。
使用Ruby脚本构建集群
在{REDIS_HOME}/src目录下,可以看到redis-trib.rb文件,这是一个可以实现自动建簇的Ruby脚本。
安装Ruby环境
以Ubuntu为例,Ruby环境可以安装如下:
Apt-get install Ruby #安装Ruby环境。
Gem安装redis #gem是Ruby 的包管理工具。这个命令可以安装ruby-redis依赖项。
开始节点
与启动节点在第一种方法中。
建立集群。
redis-trib.rb脚本提供了许多命令,其中create用于构建集群。使用方法如下:/redis-trib . rbcreate-replication 192 . 168 . 72 . 128:7000192 . 168 . 72 . 128:7001192 . 168 . 72 . 128:7002192 . 168 . 72 . 128:8000192 . 168 . 72 . 128:800192 . 168 . 72 . 128:800192 . 168 . 72 . 128:8002
其中:-Replicas=1表示每个主节点有一个从节点;后面的{ip:port}表示节点地址,前者为主节点,后者为从节点。使用redis-trib.rb构建集群时,要求节点不能包含任何插槽和数据。
执行create命令后,脚本会给出创建集群的计划,如下图所示;包括规划哪些是主节点,哪些是从节点,以及如何分配时隙。
输入yes确认执行计划,脚本将按照计划执行,如下图所示:
至此,集群构建完成。
集群方案设计
设计集群方案时,至少应考虑以下因素:
高可用性要求:根据故障转移原理,至少需要三个主节点才能完成故障转移,且三个主节点不能在同一台物理机上。
每个主节点至少需要一个从节点,主节点和从节点不要在一台物理机上;因此,高可用性集群至少包含6个节点。
数据量和访问量:估算应用需要的数据量和总访问量(考虑业务发展,留有冗余),结合每个主节点的容量和可以承受的访问量(可以通过基准精确估算),计算出需要的主节点数量。
节点数:Redis官方给出的节点数限制在1000个,主要是考虑到节点间通信造成的消耗。
在实际应用中,应尽可能避免大的集群。如果节点数量不足以满足应用对Redis数据量和访问的要求,可以考虑如下:业务分割,将大集群分成几个小集群;减少不必要的数据;调整数据过期策略等。
适度冗余:Redis可以在不影响集群服务的情况下增加节点,所以节点数量要适当冗余,不要太多。
聚类的基本原理
以上介绍了集群的搭建方法和设计方案,下面将进一步介绍集群的原理。
集群的核心功能是数据分区,所以:
首先,介绍了数据的划分规则。
然后介绍了集群实现的细节:通信机制和数据结构。
最后以cluster meet(节点握手)和cluster addslots(插槽分配)为例,展示了节点如何利用上述数据结构和通信机制实现集群命令。
数据分区方案
数据划分包括顺序划分和哈希划分,其中哈希划分因其天然的随机性而被广泛使用。簇分区方案是一种散列分区。
分区的基本思想是对数据的特征值(比如key)进行散列,然后根据散列值决定数据落在哪个节点上。
常见的散列分区包括:散列余数分区、一致散列分区、带有虚拟节点的一致散列分区等。
衡量数据划分方法质量的标准有很多,其中两个重要因素是:
数据是否均匀分布。
添加或删除节点对数据分布的影响。
由于hash的随机性,hash分区基本可以保证数据的均匀分布;因此,在比较哈希划分方案时,要重点考虑增加或减少节点对数据分布的影响。
哈希剩余分区
哈希冗余划分的思路很简单:计算key的哈希值,然后对节点数进行冗余,从而确定数据映射到哪个节点。
这种方案最大的问题是,当增加或删除节点时,节点数量发生变化,系统中的所有数据都需要重新计算映射关系,导致大规模的数据迁移。
一致性散列分区
哈希算法将整个哈希空间组织成一个虚拟的圆,如下图所示,范围为0-2 32-1。
对于每一个数据,根据key计算hash值,确定数据在环上的位置,然后从这个位置开始沿着环顺时针走。找到的第一个服务器是它应该映射到的服务器。
与哈希剩余划分相比,一致哈希划分限制了o的影响
上图就是一个例子。如果在node1和node2之间增加node5,node2中只有部分数据会迁移到Node 5;如果删除节点2,原始节点2中的数据只会迁移到节点4,并且只有节点4会受到影响。
哈希分区的主要问题是,当节点数量较少时,增加或删除节点可能会对单个节点产生很大影响,导致数据严重失衡。
以上图为例。如果去掉node2,node4中的数据将从总数据的1/4左右变为1/2左右,与其他节点相比负载过高。
具有虚拟节点的一致散列分区
该方案在一致哈希划分的基础上,引入了虚拟节点的概念。Redis集群使用这种方案,其中的虚拟节点称为slot。
Slot是数据和实际节点之间的虚拟概念;每个实际节点包含一定数量的片段,每个片段包含具有一定范围的散列值的数据。
引入槽后,数据的映射关系从数据哈希-实际节点变成了数据哈希-槽-实际节点。
在使用槽的一致散列分区中,槽是数据管理和迁移的基本单位。插槽解耦了数据和实际节点之间的关系,添加或删除节点对系统的影响很小。
以上图为例,系统中有4个实际节点,假设给它们分配了16个时隙(0-15);插槽0-3位于节点1,插槽4-7位于节点2,依此类推。
如果此时删除node2,只需要重新分配槽4-7,比如槽4-5分配给node1,槽6分配给node3,槽7分配给Node 4;可以看出,删除node2后,其他节点的数据分布还是比较均衡的。
一般来说,槽的数量远远小于2 ^ 32,远远大于实际节点的数量。在Redis集群中,插槽的数量是16384。
上图很好地总结了Redis集群将数据映射到实际节点的过程:
Redis计算数据的特征值(通常是key)的哈希值,使用的算法是CRC16。
根据哈希值,计算数据属于哪个槽。
根据插槽和节点的映射关系,计算数据属于哪个节点。
节点通信机制
集群可以没有节点间的通信,就不能作为一个整体工作。
两个端口
在哨兵系统中,节点分为数据节点和哨兵节点:前者存储数据,后者实现附加控制功能。
在集群中,数据节点和非数据节点没有区别:所有节点都存储数据并参与集群状态的维护。
因此,群集中的每个节点都提供两个TCP端口:
正常端口:即我们前面指定的端口(7000等。).普通端口主要用于为客户端提供服务(类似于单机节点);但是它也将用于节点之间的数据迁移。
集群端口:端口号为普通端口10000(10000为固定值,不可更改),如7000节点的集群端口为17000。
群集的端口仅用于节点之间的通信,例如在设置群集、添加或删除节点以及故障转移等操作期间节点之间的通信。不要使用客户端连接到集群接口。为了保证集群的正常运行,在配置防火墙时,公共端口和集群端口应该同时打开。
八卦协议
节点之间的通信根据通信协议可以分为几种类型:一对一通信、广播通信、八卦协议等。重点是广播和八卦的比较。
广播是向集群中的所有节点发送消息;优点是集群的收敛速度快(集群收敛是指集群中的所有节点都获得相同的集群信息),缺点是每条消息都要发送给所有节点,消耗大量的CPU和带宽。
Gossip协议的特点是在节点数量有限的网络中,每个节点与一些节点进行通信随机(不是真的随机,而是按照特定规则进行通信的节点)。经过一番混沌通信,各个节点的状态很快就会一致。
Gossip协议的优点包括负载更低(比广播)、去中心化、高容错性(因为通信的冗余性)等。主要缺点是集群收敛速度慢。
消息类型
集群中的节点使用固定频率(每秒10次)的调度任务进行通信相关的工作:判断是否发送消息和消息类型,确定接收节点,发送消息等。
如果集群的状态发生变化,比如增加或减少节点,改变槽位状态,所有节点都会通过节点间的通信迅速知道整个集群的状态,使集群收敛。
节点间发送的消息主要分为五种类型:
会议消息
PING消息
乒乓新闻
失败消息
发布消息
不同的消息类型、通信协议、发送频率和定时、接收节点的选择等。是不同的:
相遇消息:在节点握手阶段,节点收到客户端的集群相遇命令时,会向新加入的节点发送相遇消息,请求新节点加入当前集群;新节点将在接收到会议消息后回复PONG消息。
PING报文:集群中的每个节点每秒都会选择一些节点发送PING报文,接收方收到报文后会回复一个PONG报文。PING报文的内容是自身节点和其他一些节点的状态信息;功能是互相交换信息,检测节点是否在线。
PING报文采用Gossip协议发送,接收节点的选择考虑了收敛速度和带宽成本。具体规则如下:随机找五个节点,选择最长时间没有通信的节点。扫描节点列表,选择最后一次PONG消息接收时间大于cluster_node_timeout/2的所有节点,防止这些节点长时间更新。
PONG消息:PONG消息封装了自己的状态数据。可以分为两种:第一种是收到MEET/PING消息后应答的PONG消息;第二种方式是节点向集群广播PONG消息。
这样其他节点就可以知道这个节点的最新信息,比如新的主节点在故障恢复后会广播PONG消息。
失败消息:当一个主节点判断另一个主节点已经进入失败状态时,它会向集群广播这个失败消息;接收节点将保存该失败消息用于后续判断。
发布消息:节点收到发布命令后,会先执行命令,然后向集群广播消息,接收节点也会执行发布命令。
数据结构
节点需要一个特殊的数据结构来存储集群的状态。所谓集群状态是一个很大的概念,包括:集群是否在线,集群中有哪些节点,节点是否可达,节点的主从状态,插槽的分布.
在节点提供的存储集群状态的数据结构中,最关键的是clusterNode和集群状态结构:前者记录一个节点的状态,后者记录整个集群的状态。
集群节点
ClusterNode结构保存节点的当前状态,包括创建时间、节点id、ip和端口号等。
每个节点用一个clusterNode结构记录自己的状态,并为集群中的所有其他节点创建一个clusterNode结构来记录节点状态。
下面列出了clusterNode的一些字段,并解释了它们的含义和功能:
TypedefstructclusterNode {///节点创建时间mstime _ tctime//node idcharname[redis _ cluster _ namelen];//节点的ip和端口号Charip[redis _ IP _ str _ len];intport//节点ID:整数,每一位代表不同的状态,比如节点的主从状态,是否在线,握手等intflags//配置纪元:在故障转移时工作,类似于Sentinel的配置纪元uint64 _ tconfigEpoch//本节点中的槽分布:16384/8字节,16384位;每个位对应一个槽:当位值为1时,该位对应的槽在节点中;当位值为0时,对应于该位的槽不在节点无符号槽[16384/8]中;//节点intnumslots中的槽数;…………} cluster node;
此外,clusterState还包括故障转移、插槽迁移等所需的信息。
集群命令的实现
在这一部分中,我们将以cluster meet和cluster addslots为例来说明节点如何使用上述数据结构和通信机制来实现集群命令。
集群会议
假设你要向节点A发送一个集群meet命令,将节点B加入到节点A所在的集群中。收到命令后,节点A将执行以下操作:
为B创建一个clusterNode结构,并将其添加到clusterState的节点字典中。
向a B发送会议消息.
在接收到MEET消息后,b将为a创建一个clusterNode结构,并将其添加到clusterState的节点字典中。
回复一条乒乓消息。
收到B发来的PONG消息后,我知道B已经成功收到了自己的MEET消息。
然后,A向b返回PING消息。
收到B发来的PING报文后,知道A已经成功接收到自己的PONG报文,握手完成。
之后,A广播B的信息通过Gossip协议传递给集群中的其他节点,其他节点会和B握手;一段时间后,集群收敛,B成为集群中的一个普通节点。
通过上面的过程可以发现,集群中两个节点的握手过程类似于TCP,是三次握手:A发送MEETto B;发PONGto b a;向a B发送PING.
集群添加插槽
集群中插槽的分配信息存储在clusterNode的插槽数组和clusterState的插槽数组中。前面已经介绍了这两个数组的结构。
两者的区别在于前者存储的是本节点分配了哪些槽,后者存储的是集群中所有槽都分布在哪些节点。
cluster addslots命令接收一个或多个插槽作为参数。例如,当群集添加插槽{0.10}命令在节点A上执行,编号为0-10的槽被分配给节点A
具体实施过程如下:
遍历输入槽以检查它们是否未被分配。如果分配了一个槽,则命令执行失败;方法是检查clusterState.slots[]中输入槽的对应值是否为空。
遍历输入槽并将其分配给节点A;方法是修改clusterNode.slots[]中对应的位为1,clusterState.slots[]中对应的指针指向节点a
节点A执行完毕后,通过节点通信机制通知其他节点,所有节点都会知道时隙0-10分配给了节点A。
客户端访问群集
在一个集群中,数据分布在不同的节点上,当一个客户端通过一个节点访问数据时,数据可能不在那个节点上;这里这就是集群处理这个问题的方式。
redis-cli
当节点从redis-cli收到命令(如set/get)时,流程如下:
计算钥匙属于哪个槽:CRC16(钥匙)16383。
集群提供的集群keyslot命令也是通过使用上述公式实现的,例如:
判断键所在的槽是否在当前节点:假设键位于第I个槽,clusterState.slots[i]指向该槽所在的节点。
如果cluster state . slots[I]==cluster state . my,则表示该槽在当前节点,可以在当前节点直接执行命令。
否则说明该槽不在当前节点,那么查询该槽所在节点的地址(clusterState.slots[i])。ip/port),将其包装在一个移动的错误中,并将其返回给redis-cli。
redis-cli收到移动错误后,根据返回的ip和端口重新发送请求。
下面的例子展示了redis-cli与集群的交互:key1在节点7000操作,但是key1所在的槽9189在节点7001。
因此,节点向redis-cli返回一个移动错误(包括7001节点的ip和端口),redis-cli重新向7001发起请求。
在上面的示例中,redis-Cli通过-c指定集群模式。如果未指定,redis-cli将无法处理移动的错误:
智能客户端
像redis-cli这样的客户端被称为虚拟客户端,因为它们不会在执行命令之前,不知道数据在哪个节点,需要借助移动错误来重定向数据。与虚拟客户端相对应的是智能客户端。
智能客户端(以Java的JedisCluster为例)的基本原理如下:
当JedisCluster初始化时,slot-node的缓存在内部维护。方法是连接任何节点并执行集群插槽命令,该命令返回如下:
此外,JedisCluster为每个节点创建一个连接池(即JedisPool)。
执行命令时,JedisCluster根据key-slot-node选择要连接的节点,并发送命令。
如果成功,则命令执行完成;如果执行失败,将随机选择其他节点重试,当发生移动错误时,将使用集群槽重新同步槽-节点的映射关系。
下面的代码演示了如何使用JedisCluster访问集群(不考虑资源释放、异常处理等)。):
publistaticvoidtest(){ set nodes=new hashset();nodes . add(newHostAndPort(192.168.72.128'7000));nodes . add(newHostAndPort(192.168.72.128'7001));nodes . add(newHostAndPort(192.168.72.128'7002));nodes . add(newHostAndPort(192.168.72.128'8000));nodes . add(newHostAndPort(192.168.72.128'8001));nodes . add(newHostAndPort(192.168.72.128'8002));JedisClustercluster=new jediscluster(节点);system . out . println(cluster . get(key1 ));cluster . close();}
注意事项如下:
JedisCluster已经包含了所有节点的连接池,所以JedisCluster应该使用singletons。
客户端维护插槽-节点映射关系,并为每个节点创建一个连接池。当节点数量较大时,要注意客户端内存资源和连接资源的消耗。
新版Jedis对JedisCluster做了一些性能优化,比如集群槽缓存更新和锁阻塞。应尽可能使用Jedis 2.8.2及以上版本。
练习笔记
前面介绍了集群的正常操作和访问的方法和原理,下面是一些重要的补充内容。
集群扩展
在实践中,经常需要对集群进行伸缩,比如当访问次数增加时的扩展操作。Redis集群可以在不影响外部服务的情况下进行扩展;伸缩的核心是槽迁移:修改槽与节点的对应关系,实现槽(即数据)在节点间的移动。
例如,如果槽均匀分布在集群中的三个节点中,此时增加一个节点,则需要从三个节点中取出一些槽到新节点,从而实现槽在四个节点中的均匀分布。
添加节点
假设您要添加7003和8003节点,其中8003是7003的从节点。步骤如下:
启动节点:方法见集群构建。
与节点握手:可以使用cluster meet命令,但建议在生产环境中使用redis-trib.rb的add-node工具。它的原理也是cluster meet,但是它会先检查新节点是否加入了其他集群或者有数据,避免加入集群后混淆。
redis-trib . rbadd-node 192 . 168 . 72 . 128:7003192 . 168 . 72 . 1287000
迁移槽:推荐使用redis-trib.rb的reshard工具实现。Reshard自动化程度很高,只需输入redis-trib.rb reshard ip:port (ip和port可以是集群中的任意节点)。
然后按照提示输入以下信息,插槽迁移将自动完成:
要迁移的插槽数量:16,384个插槽平均分配给4个节点,每个节点有4,096个插槽,因此要迁移的插槽数量为4,096个。
目标id: 7003节点的id。
节点Id:7000/7001/7002节点ID。
指定主从关系:方法参见集群构建。
减少节点
假设7000/8000个节点将离线,这可以分为两个步骤:
迁移插槽:使用reshard将7000节点中的插槽均匀地迁移到7001/7002/7003节点。
离线节点:使用redis-trib.rb del-node工具;从节点应该在主节点之前脱机,因为如果主节点先脱机,从节点会被指向其他主节点,导致不必要的完整复制。
Redis-trib . rbdel-node 192 . 168 . 72 . 128:7001 { node 8000的ID }
询问错误
集群扩展的核心是插槽迁移。在插槽迁移过程中,如果客户端向源节点发送命令,源节点的执行过程如下:
接收到ASK错误后,客户端从中读取目标节点的地址信息,并向目标节点重新发送请求,就像接收移动错误时一样。
但是两者有很大的区别:ASK error表示数据正在迁移,何时完成迁移是未知的,所以重定向是临时的,智能客户端不会刷新slots缓存;移动错误重定向是(相对)永久的,智能客户端刷新插槽缓存。
故障切换
在《哨兵》一文中,哨兵的原则的故障发现和故障转移。
虽然细节差别较大,但集群的实现和哨兵类似:PING消息由调度任务发送,检测其他节点的状态;节点下线分为主观下线和客观下线;客观脱机后,选择从节点进行故障转移。
和哨兵一样,集群只实现主节点的故障转移;当从节点出现故障时,它只会脱机,不会进行故障转移。
因此,在使用集群时,要谨慎使用读写分离技术,因为从节点失效会导致读取服务不可用,可用性差。
这里,将不详细描述故障转移的细节,而仅解释重要的事项:
节点数量:在故障转移阶段,主节点需要投票决定哪个从节点成为新的主节点;从节点选举中胜出所需票数为n/21;其中n是主节点的数量(包括故障主节点),但故障主节点可以实际上不投票。
因此,为了在发生故障时成功地选择从节点,集群中至少需要三个主节点(并且部署在不同的物理机器上)。
故障转移时间:从主节点故障到转移完成,所需时间主要消耗在主观离线识别、主观离线传播、选举延迟等环节。
具体时间与参数cluster-node-timeout有关。一般来说:
故障转移时间(毫秒)1.5 *群集节点超时1000。
群集节点超时的默认值为15000毫秒(15秒),因此故障转移时间大约为20秒。
集群的局限性及对策。
因为群集中的数据分布在不同的节点上,所以一些功能受到限制,包括:
密钥批处理操作受到限制:例如,只有当操作的所有密钥都位于一个槽中时,才能执行mget和mset操作。
解决这个问题的一个思路是在客户端记录槽和键的信息,执行mget/mset;每次用于特定的时隙;另一个想法是使用散列标签。
keys/flushall等操作:keys/flushall等操作可以在任何节点上执行,但结果只针对当前节点。例如,keys操作只返回当前节点的所有键。
要解决这个问题,可以使用客户机上的集群节点来获取所有节点信息,并在所有主节点上执行keys/flushall等操作。
Transaction /Lua脚本:集群支持Transaction和Lua脚本,但前提是涉及的键必须在同一个节点。哈希标签可以解决这个问题。
数据库:一个Redis节点可以支持16个数据库,在集群模式下只支持一个数据库db0。
复制结构:只支持一层复制结构,不支持嵌套。
哈希标签
哈希的原理是:当一个键包含{}时,不哈希整个键,只哈希{}中包含的字符串。
Hash标签允许不同的键有相同的hash值,分布在同一个槽中;这样,对不同的键(mget/mset等)进行批量操作。),以及事务、Lua脚本等。可以支持。
但是,散列标签可能会引起数据分布不均匀的问题,因此有必要:
调整不同节点的槽数,使数据分布尽可能均匀。
避免对热数据使用散列标签,这会导致请求分布不均匀。
下面是一个使用Hash标签的例子:通过给产品添加Hash标签,可以将所有的产品信息放在同一个槽位,方便操作。
参数最优化
群集节点超时
前面已经初步介绍了Cluster_node_timeout参数;默认值为15s,其影响包括:
影响PING报文接收节点的选择:值越大,延迟容忍度越高,选择的接收节点越少,可以减少带宽,但会降低收敛速度;应根据带宽和应用要求进行调整。
影响故障转移的判断和时间:值越大,越不容易误判,但完成故障转移的时间越长;应根据网络条件和应用要求进行调整。
集群要求全覆盖
如前所述,只有当分配完所有16,384个插槽时,集群才能联机。这样做是为了确保集群的完整性。
同时也带来了新的问题:当主节点出现故障,故障转移还没有完成,而原来主节点中的槽不在任何一个节点时,集群就会离线,无法响应客户端的要求。
cluster-require-full-coverage参数可以更改此设置:如果将其设置为no,则当插槽未完全分配时,群集仍然可以联机。
默认值为是。如果应用程序需要高可用性,可以将其更改为no,但您需要确保所有的插槽都由您自己分配。
redis-trib.rb
Redis-trib.rb提供了许多实用的工具:创建集群、添加或删除节点、迁移插槽、检查完整性、重新平衡数据等。您可以通过help命令查看详细信息。
如果在实践中能使用redis-trib.rb工具,尽量使用,不仅方便快捷,还能大大降低出错概率。