在前面的文章中,已经介绍了Redis的几种高可用技术:持久化、主从复制和哨兵,但这些方案仍有不足,其中最主要的问题是存储能力受单机限制,以及没法实现写操做的负载均衡。html
Redis集群解决了上述问题,实现了较为完善的高可用方案。本文将详细介绍集群,主要内容包括:集群的做用;集群的搭建方法及设计方案;集群的基本原理;客户端访问集群的方法;以及其余实践中须要的集群知识(集群扩容、故障转移、参数优化等)。node
1. 执行Redis命令搭建集群linux
2. 使用Ruby脚本搭建集群c++
3. 集群方案设计redis
1. 数据分区方案算法
2. 节点通讯机制数据库
3. 数据结构编程
4. 集群命令的实现数组
1. redis-cli缓存
2. Smart客户端
1. 集群伸缩
2. 故障转移
3. 集群的限制及应对方法
4. Hash Tag
5. 参数优化
6. redis-trib.rb
1、集群的做用
集群,即Redis Cluster,是Redis 3.0开始引入的分布式存储方案。
集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。
集群的做用,能够概括为两点:
一、数据分区:数据分区(或称数据分片)是集群最核心的功能。
集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增长;另外一方面每一个主节点均可以对外提供读服务和写服务,大大提升了集群的响应能力。
Redis单机内存大小受限问题,在介绍持久化和主从复制时都有说起;例如,若是单机内存太大,bgsave和bgrewriteaof的fork操做可能致使主进程阻塞,主从环境下主机切换时可能致使从节点长时间没法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……。
二、高可用:集群支持主从复制和主节点的自动故障转移(与哨兵相似);当任一节点发生故障时,集群仍然能够对外提供服务。
本文内容基于Redis 3.0.6。
2、集群的搭建这一部分咱们将搭建一个简单的集群:共6个节点,3主3从。方便起见:全部节点在同一台服务器上,以端口号进行区分;配置从简。3个主节点端口号:7000/7001/7002,对应的从节点端口号:8000/8001/8002。
集群的搭建有两种方式:(1)手动执行Redis命令,一步步完成搭建;(2)使用Ruby脚本搭建。两者搭建的原理是同样的,只是Ruby脚本将Redis命令进行了打包封装;在实际应用中推荐使用脚本方式,简单快捷不容易出错。下面分别介绍这两种方式。
集群的搭建能够分为四步:(1)启动节点:将节点以集群模式启动,此时节点是独立的,并无创建联系;(2)节点握手:让独立的节点连成一个网络;(3)分配槽:将16384个槽分配给主节点;(4)指定主从关系:为从节点指定主节点。
实际上,前三步完成后集群即可以对外提供服务;但指定从节点后,集群才可以提供真正高可用的服务。
集群节点的启动仍然是使用redis-server命令,但须要使用集群模式启动。下面是7000节点的配置文件(只列出了节点正常工做关键配置,其余配置(如开启AOF)能够参照单机节点进行):
#redis-7000.conf port 7000 cluster-enabled yes cluster-config-file "node-7000.conf" logfile "log-7000.log" dbfilename "dump-7000.rdb" daemonize yes
cluster-enabled yes:Redis实例能够分为单机模式(standalone)和集群模式(cluster);cluster-enabled yes能够启动集群模式。在单机模式下启动的Redis实例,若是执行info server命令,能够发现redis_mode一项为standalone,以下图所示:其中的cluster-enabled和cluster-config-file是与集群相关的配置。
集群模式下的节点,其redis_mode为cluster,以下图所示:
cluster-config-file:该参数指定了集群配置文件的位置。每一个节点在运行过程当中,会维护一份集群配置文件;每当集群信息发生变化时(如增减节点),集群内全部节点会将最新信息更新到该配置文件;当节点重启后,会从新读取该配置文件,获取集群信息,能够方便的从新加入到集群中。也就是说,当Redis节点以集群模式启动时,会首先寻找是否有集群配置文件,若是有则使用文件中的配置启动,若是没有,则初始化配置并将配置保存到文件中。集群配置文件由Redis节点维护,不须要人工修改。
编辑好配置文件后,使用redis-server命令启动该节点:
redis-server redis-7000.conf
节点启动之后,经过cluster nodes命令能够查看节点的状况,以下图所示。
其中返回值第一项表示节点id,由40个16进制字符串组成,节点id与 主从复制 一文中提到的runId不一样:Redis每次启动runId都会从新建立,可是节点id只在集群初始化时建立一次,而后保存到集群配置文件中,之后节点从新启动时会直接在集群配置文件中读取。
其余节点使用相同办法启动,再也不赘述。须要特别注意,在启动节点阶段,节点是没有主从关系的,所以从节点不须要加slaveof配置。
节点启动之后是相互独立的,并不知道其余节点存在;须要进行节点握手,将独立的节点组成一个网络。
节点握手使用cluster meet {ip} {port}命令实现,例如在7000节点中执行cluster meet 192.168.72.128 7001,能够完成7000节点和7001节点的握手;注意ip使用的是局域网ip而不是localhost或127.0.0.1,是为了其余机器上的节点或客户端也能够访问。此时再使用cluster nodes查看:
在7001节点下也能够相似查看:
同理,在7000节点中使用cluster meet命令,能够将全部节点加入到集群,完成节点握手:
cluster meet 192.168.72.128 7002 cluster meet 192.168.72.128 8000 cluster meet 192.168.72.128 8001 cluster meet 192.168.72.128 8002
执行完上述命令后,能够看到7000节点已经感知到了全部其余节点:
经过节点之间的通讯,每一个节点均可以感知到全部其余节点,以8000节点为例:
在Redis集群中,借助槽实现数据分区,具体原理后文会介绍。集群有16384个槽,槽是数据管理和迁移的基本单位。当数据库中的16384个槽都分配了节点时,集群处于上线状态(ok);若是有任意一个槽没有分配节点,则集群处于下线状态(fail)。
cluster info命令能够查看集群状态,分配槽以前状态为fail:
分配槽使用cluster addslots命令,执行下面的命令将槽(编号0-16383)所有分配完毕:
redis-cli -p 7000 cluster addslots {0..5461} redis-cli -p 7001 cluster addslots {5462..10922} redis-cli -p 7002 cluster addslots {10923..16383}
此时查看集群状态,显示全部槽分配完毕,集群进入上线状态:
集群中指定主从关系再也不使用slaveof命令,而是使用cluster replicate命令;参数使用节点id。
经过cluster nodes得到几个主节点的节点id后,执行下面的命令为每一个从节点指定主节点:
redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4 redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1
此时执行cluster nodes查看各个节点的状态,能够看到主从关系已经创建。
至此,集群搭建完毕。
在{REDIS_HOME}/src目录下能够看到redis-trib.rb文件,这是一个Ruby脚本,能够实现自动化的集群搭建。
(1)安装Ruby环境
以Ubuntu为例,以下操做便可安装Ruby环境:
apt-get install ruby #安装ruby环境 gem install redis #gem是ruby的包管理工具,该命令能够安装ruby-redis依赖
与第一种方法中的“启动节点”彻底相同。(2)启动节点
(3)搭建集群
redis-trib.rb脚本提供了众多命令,其中create用于搭建集群,使用方法以下:
./redis-trib.rb create --replicas 1 192.168.72.128:7000 192.168.72.128:7001 192.168.72.128:7002 192.168.72.128:8000 192.168.72.128:8001 192.168.72.128:8002
执行建立命令后,脚本会给出建立集群的计划,以下图所示;计划包括哪些是主节点,哪些是从节点,以及如何分配槽。其中:--replicas=1表示每一个主节点有1个从节点;后面的多个{ip:port}表示节点地址,前面的作主节点,后面的作从节点。使用redis-trib.rb搭建集群时,要求节点不能包含任何槽和数据。
输入yes确认执行计划,脚本便开始按照计划执行,以下图所示。
至此,集群搭建完毕。
设计集群方案时,至少要考虑如下因素:
(1)高可用要求:根据故障转移的原理,至少须要3个主节点才能完成故障转移,且3个主节点不该在同一台物理机上;每一个主节点至少须要1个从节点,且主从节点不该在一台物理机上;所以高可用集群至少包含6个节点。
(2)数据量和访问量:估算应用须要的数据量和总访问量(考虑业务发展,留有冗余),结合每一个主节点的容量和能承受的访问量(能够经过benchmark获得较准确估计),计算须要的主节点数量。
(3)节点数量限制:Redis官方给出的节点数量限制为1000,主要是考虑节点间通讯带来的消耗。在实际应用中应尽可能避免大集群;若是节点数量不足以知足应用对Redis数据量和访问量的要求,能够考虑:(1)业务分割,大集群分为多个小集群;(2)减小没必要要的数据;(3)调整数据过时策略等。
(4)适度冗余:Redis能够在不影响集群服务的状况下增长节点,所以节点数量适当冗余便可,不用太大。
推荐一下本身的linuxC/C++交流群:973961276!整理了一些我的以为比较好的学习书籍、视频资料以及大厂面经视频,有须要的小伙伴能够进群获取哦!
3、集群的基本原理上一章介绍了集群的搭建方法和设计方案,下面将进一步深刻,介绍集群的原理。集群最核心的功能是数据分区,所以首先介绍数据的分区规则;而后介绍集群实现的细节:通讯机制和数据结构;最后以cluster meet(节点握手)、cluster addslots(槽分配)为例,说明节点是如何利用上述数据结构和通讯机制实现集群命令的。
数据分区有顺序分区、哈希分区等,其中哈希分区因为其自然的随机性,使用普遍;集群的分区方案即是哈希分区的一种。
哈希分区的基本思路是:对数据的特征值(如key)进行哈希,而后根据哈希值决定数据落在哪一个节点。常见的哈希分区包括:哈希取余分区、一致性哈希分区、带虚拟节点的一致性哈希分区等。
衡量数据分区方法好坏的标准有不少,其中比较重要的两个因素是(1)数据分布是否均匀(2)增长或删减节点对数据分布的影响。因为哈希的随机性,哈希分区基本能够保证数据分布均匀;所以在比较哈希分区方案时,重点要看增减节点对数据分布的影响。
(1)哈希取余分区
哈希取余分区思路很是简单:计算key的hash值,而后对节点数量进行取余,从而决定数据映射到哪一个节点上。该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中全部的数据都须要从新计算映射关系,引起大规模数据迁移。
(2)一致性哈希分区
一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,以下图所示,范围为0-2^32-1;对于每一个数据,根据key计算hash值,肯定数据在环上的位置,而后今后位置沿环顺时针行走,找到的第一台服务器就是其应该映射到的服务器。
图片来源:https://www.cnblogs.com/lpfuture/p/5796398.html
与哈希取余分区相比,一致性哈希分区将增减节点的影响限制在相邻节点。以上图为例,若是在node1和node2之间增长node5,则只有node2中的一部分数据会迁移到node5;若是去掉node2,则原node2中的数据只会迁移到node4中,只有node4会受影响。
一致性哈希分区的主要问题在于,当节点数量较少时,增长或删减节点,对单个节点的影响可能很大,形成数据的严重不平衡。仍是以上图为例,若是去掉node2,node4中的数据由总数据的1/4左右变为1/2左右,与其余节点相比负载太高。
(3)带虚拟节点的一致性哈希分区
该方案在一致性哈希分区的基础上,引入了虚拟节点的概念。Redis集群使用的即是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念;每一个实际节点包含必定数量的槽,每一个槽包含哈希值在必定范围内的数据。引入槽之后,数据的映射关系由数据hash->实际节点,变成了数据hash->槽->实际节点。
在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系,增长或删除节点对系统的影响很小。仍以上图为例,系统中有4个实际节点,假设为其分配16个槽(0-15); 槽0-3位于node1,4-7位于node2,以此类推。若是此时删除node2,只须要将槽4-7从新分配便可,例如槽4-5分配给node1,槽6分配给node3,槽7分配给node4;能够看出删除node2后,数据在其余节点的分布仍然较为均衡。
槽的数量通常远小于2^32,远大于实际节点的数量;在Redis集群中,槽的数量为16384。
下面这张图很好的总结了Redis集群将数据映射到实际节点的过程:
图片修改自:https://blog.csdn.net/yejingtao703/article/details/78484151
(1)Redis对数据的特征值(通常是key)计算哈希值,使用的算法是CRC16。
(2)根据哈希值,计算数据属于哪一个槽。
(3)根据槽与节点的映射关系,计算数据属于哪一个节点。
集群要做为一个总体工做,离不开节点之间的通讯。
在哨兵系统中,节点分为数据节点和哨兵节点:前者存储数据,后者实现额外的控制功能。在集群中,没有数据节点与非数据节点之分:全部的节点都存储数据,也都参与集群状态的维护。为此,集群中的每一个节点,都提供了两个TCP端口:
节点间通讯,按照通讯协议能够分为几种类型:单对单、广播、Gossip协议等。重点是广播和Gossip的对比。
广播是指向集群内全部节点发送消息;优势是集群的收敛速度快(集群收敛是指集群内全部节点得到的集群信息是一致的),缺点是每条消息都要发送给全部节点,CPU、带宽等消耗较大。
Gossip协议的特色是:在节点数量有限的网络中,每一个节点都“随机”的与部分节点通讯(并非真正的随机,而是根据特定的规则选择通讯的节点),通过一番杂乱无章的通讯,每一个节点的状态很快会达到一致。Gossip协议的优势有负载(比广播)低、去中心化、容错性高(由于通讯有冗余)等;缺点主要是集群的收敛速度慢。
集群中的节点采用固定频率(每秒10次)的定时任务进行通讯相关的工做:判断是否须要发送消息及消息类型、肯定接收节点、发送消息等。若是集群状态发生了变化,如增减节点、槽状态变动,经过节点间的通讯,全部节点会很快得知整个集群的状态,使集群收敛。
节点间发送的消息主要分为5种:meet消息、ping消息、pong消息、fail消息、publish消息。不一样的消息类型,通讯协议、发送的频率和时机、接收节点的选择等是不一样的。
节点须要专门的数据结构来存储集群的状态。所谓集群的状态,是一个比较大的概念,包括:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的分布……
节点为了存储集群状态而提供的数据结构中,最关键的是clusterNode和clusterState结构:前者记录了一个节点的状态,后者记录了集群做为一个总体的状态。
clusterNode结构保存了一个节点的当前状态,包括建立时间、节点id、ip和端口号等。每一个节点都会用一个clusterNode结构记录本身的状态,并为集群内全部其余节点都建立一个clusterNode结构来记录节点状态。
下面列举了clusterNode的部分字段,并说明了字段的含义和做用:
typedef struct clusterNode { //节点建立时间 mstime_t ctime; //节点id char name[REDIS_CLUSTER_NAMELEN]; //节点的ip和端口号 char ip[REDIS_IP_STR_LEN]; int port; //节点标识:整型,每一个bit都表明了不一样状态,如节点的主从状态、是否在线、是否在握手等 int flags; //配置纪元:故障转移时起做用,相似于哨兵的配置纪元 uint64_t configEpoch; //槽在该节点中的分布:占用16384/8个字节,16384个比特;每一个比特对应一个槽:比特值为1,则该比特对应的槽在节点中;比特值为0,则该比特对应的槽不在节点中 unsigned char slots[16384/8]; //节点中槽的数量 int numslots; ………… } clusterNode;
clusterState除了上述字段,clusterNode还包含节点链接、主从复制、故障发现和转移须要的信息等。
clusterState结构保存了在当前节点视角下,集群所处的状态。主要字段包括:
typedef struct clusterState { //自身节点 clusterNode *myself; //配置纪元 uint64_t currentEpoch; //集群状态:在线仍是下线 int state; //集群中至少包含一个槽的节点数量 int size; //哈希表,节点名称->clusterNode节点指针 dict *nodes; //槽分布信息:数组的每一个元素都是一个指向clusterNode结构的指针;若是槽尚未分配给任何节点,则为NULL clusterNode *slots[16384]; ………… } clusterState;
4. 集群命令的实现除此以外,clusterState还包括故障转移、槽迁移等须要的信息。
这一部分将以cluster meet(节点握手)、cluster addslots(槽分配)为例,说明节点是如何利用上述数据结构和通讯机制实现集群命令的。
假设要向A节点发送cluster meet命令,将B节点加入到A所在的集群,则A节点收到命令后,执行的操做以下:
1) A为B建立一个clusterNode结构,并将其添加到clusterState的nodes字典中
2) A向B发送MEET消息
3) B收到MEET消息后,会为A建立一个clusterNode结构,并将其添加到clusterState的nodes字典中
4) B回复A一个PONG消息
5) A收到B的PONG消息后,便知道B已经成功接收本身的MEET消息
6) 而后,A向B返回一个PING消息
7) B收到A的PING消息后,便知道A已经成功接收本身的PONG消息,握手完成
8) 以后,A经过Gossip协议将B的信息广播给集群内其余节点,其余节点也会与B握手;一段时间后,集群收敛,B成为集群内的一个普通节点
经过上述过程能够发现,集群中两个节点的握手过程与TCP相似,都是三次握手:A向B发送MEET;B向A发送PONG;A向B发送PING。
集群中槽的分配信息,存储在clusterNode的slots数组和clusterState的slots数组中,两个数组的结构前面已作介绍;两者的区别在于:前者存储的是该节点中分配了哪些槽,后者存储的是集群中全部槽分别分布在哪一个节点。
cluster addslots命令接收一个槽或多个槽做为参数,例如在A节点上执行cluster addslots {0..10}命令,是将编号为0-10的槽分配给A节点,具体执行过程以下:
1) 遍历输入槽,检查它们是否都没有分配,若是有一个槽已分配,命令执行失败;方法是检查输入槽在clusterState.slots[]中对应的值是否为NULL。
2) 遍历输入槽,将其分配给节点A;方法是修改clusterNode.slots[]中对应的比特为1,以及clusterState.slots[]中对应的指针指向A节点
3) A节点执行完成后,经过节点通讯机制通知其余节点,全部节点都会知道0-10的槽分配给了A节点
4、客户端访问集群在集群中,数据分布在不一样的节点中,客户端经过某节点访问数据时,数据可能不在该节点中;下面介绍集群是如何处理这个问题的。
当节点收到redis-cli发来的命令(如set/get)时,过程以下:
(1)计算key属于哪一个槽:CRC16(key) & 16383
集群提供的cluster keyslot命令也是使用上述公式实现,如:
(2)判断key所在的槽是否在当前节点:假设key位于第i个槽,clusterState.slots[i]则指向了槽所在的节点,若是clusterState.slots[i]==clusterState.myself,说明槽在当前节点,能够直接在当前节点执行命令;不然,说明槽不在当前节点,则查询槽所在节点的地址(clusterState.slots[i].ip/port),并将其包装到MOVED错误中返回给redis-cli。
(3)redis-cli收到MOVED错误后,根据返回的ip和port从新发送请求。
下面的例子展现了redis-cli和集群的互动过程:在7000节点中操做key1,但key1所在的槽9189在节点7001中,所以节点返回MOVED错误(包含7001节点的ip和port)给redis-cli,redis-cli从新向7001发起请求。
上例中,redis-cli经过-c指定了集群模式,若是没有指定,redis-cli没法处理MOVED错误:
redis-cli这一类客户端称为Dummy客户端,由于它们在执行命令前不知道数据在哪一个节点,须要借助MOVED错误从新定向。与Dummy客户端相对应的是Smart客户端。
Smart客户端(以Java的JedisCluster为例)的基本原理:
(1)JedisCluster初始化时,在内部维护slot->node的缓存,方法是链接任一节点,执行cluster slots命令,该命令返..回以下所示:
(2)此外,JedisCluster为每一个节点建立链接池(即JedisPool)。
(3)当执行命令时,JedisCluster根据key->slot->node选择须要链接的节点,发送命令。若是成功,则命令执行完毕。若是执行失败,则会随机选择其余节点进行重试,并在出现MOVED错误时,使用cluster slots从新同步slot->node的映射关系。
下面代码演示了如何使用JedisCluster访问集群(未考虑资源释放、异常处理等):
public static void test() { Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.72.128", 7000)); nodes.add(new HostAndPort("192.168.72.128", 7001)); nodes.add(new HostAndPort("192.168.72.128", 7002)); nodes.add(new HostAndPort("192.168.72.128", 8000)); nodes.add(new HostAndPort("192.168.72.128", 8001)); nodes.add(new HostAndPort("192.168.72.128", 8002)); JedisCluster cluster = new JedisCluster(nodes); System.out.println(cluster.get("key1")); cluster.close(); }
(1)JedisCluster中已经包含全部节点的链接池,所以JedisCluster要使用单例。注意事项以下:
(2)客户端维护了slot->node映射关系以及为每一个节点建立了链接池,当节点数量较多时,应注意客户端内存资源和链接资源的消耗。
(3)Jedis较新版本针对JedisCluster作了一些性能方面的优化,如cluster slots缓存更新和锁阻塞等方面的优化,应尽可能使用2.8.2及以上版本的Jedis。
5、实践须知前面介绍了集群正常运行和访问的方法和原理,下面是一些重要的补充内容。
实践中经常须要对集群进行伸缩,如访问量增大时的扩容操做。Redis集群能够在不影响对外服务的状况下实现伸缩;伸缩的核心是槽迁移:修改槽与节点的对应关系,实现槽(即数据)在节点之间的移动。例如,若是槽均匀分布在集群的3个节点中,此时增长一个节点,则须要从3个节点中分别拿出一部分槽给新节点,从而实现槽在4个节点中的均匀分布。
假设要增长7003和8003节点,其中8003是7003的从节点;步骤以下:
(1)启动节点:方法参见集群搭建
(2)节点握手:可使用cluster meet命令,但在生产环境中建议使用redis-trib.rb的add-node工具,其原理也是cluster meet,但它会先检查新节点是否已加入其它集群或者存在数据,避免加入到集群后带来混乱。
redis-trib.rb add-node 192.168.72.128:7003 192.168.72.128 7000 redis-trib.rb add-node 192.168.72.128:8003 192.168.72.128 7000
待迁移的槽数量:16384个槽均分给4个节点,每一个节点4096个槽,所以待迁移槽数量为4096(3)迁移槽:推荐使用redis-trib.rb的reshard工具实现。reshard自动化程度很高,只须要输入redis-trib.rb reshard ip:port (ip和port能够是集群中的任一节点),而后按照提示输入如下信息,槽迁移会自动完成:
(4)指定主从关系:方法参见集群搭建
假设要下线7000/8000节点,能够分为两步:
(1)迁移槽:使用reshard将7000节点中的槽均匀迁移到7001/7002/7003节点
(2)下线节点:使用redis-trib.rb del-node工具;应先下线从节点再下线主节点,由于若主节点先下线,从节点会被指向其余主节点,形成没必要要的全量复制。
redis-trib.rb del-node 192.168.72.128:7001 {节点8000的id} redis-trib.rb del-node 192.168.72.128:7001 {节点7000的id}
图片来源:《Redis设计与实现》
客户端收到ASK错误后,从中读取目标节点的地址信息,并向目标节点从新发送请求,就像收到MOVED错误时同样。可是两者有很大区别:ASK错误说明数据正在迁移,不知道什么时候迁移完成,所以重定向是临时的,SMART客户端不会刷新slots缓存;MOVED错误重定向则是(相对)永久的,SMART客户端会刷新slots缓存。
在哨兵一文中,介绍了哨兵实现故障发现和故障转移的原理。虽然细节上有很大不一样,但集群的实现与哨兵思路相似:经过定时任务发送PING消息检测其余节点状态;节点下线分为主观下线和客观下线;客观下线后选取从节点进行故障转移。
与哨兵同样,集群只实现了主节点的故障转移;从节点故障时只会被下线,不会进行故障转移。所以,使用集群时,应谨慎使用读写分离技术,由于从节点故障会致使读服务不可用,可用性变差。
这里再也不详细介绍故障转移的细节,只对重要事项进行说明:
节点数量:在故障转移阶段,须要由主节点投票选出哪一个从节点成为新的主节点;从节点选举胜出须要的票数为N/2+1;其中N为主节点数量(包括故障主节点),但故障主节点实际上不能投票。所以为了可以在故障发生时顺利选出从节点,集群中至少须要3个主节点(且部署在不一样的物理机上)。
故障转移时间:从主节点故障发生到完成转移,所须要的时间主要消耗在主观下线识别、主观下线传播、选举延迟等几个环节;具体时间与参数cluster-node-timeout有关,通常来讲:
故障转移时间(毫秒) ≤ 1.5 * cluster-node-timeout + 1000
cluster-node-timeout的默认值为15000ms(15s),所以故障转移时间会在20s量级。
因为集群中的数据分布在不一样节点中,致使一些功能受限,包括:
(1)key批量操做受限:例如mget、mset操做,只有当操做的key都位于一个槽时,才能进行。针对该问题,一种思路是在客户端记录槽与key的信息,每次针对特定槽执行mget/mset;另一种思路是使用Hash Tag,将在下一小节介绍。
(2)keys/flushall等操做:keys/flushall等操做能够在任一节点执行,可是结果只针对当前节点,例如keys操做只返回当前节点的全部键。针对该问题,能够在客户端使用cluster nodes获取全部节点信息,并对其中的全部主节点执行keys/flushall等操做。
(3)事务/Lua脚本:集群支持事务及Lua脚本,但前提条件是所涉及的key必须在同一个节点。Hash Tag能够解决该问题。
(4)数据库:单机Redis节点能够支持16个数据库,集群模式下只支持一个,即db0。
(5)复制结构:只支持一层复制结构,不支持嵌套。
Hash Tag原理是:当一个key包含 {} 的时候,不对整个key作hash,而仅对 {} 包括的字符串作hash。
Hash Tag可让不一样的key拥有相同的hash值,从而分配在同一个槽里;这样针对不一样key的批量操做(mget/mset等),以及事务、Lua脚本等均可以支持。不过Hash Tag可能会带来数据分配不均的问题,这时须要:(1)调整不一样节点中槽的数量,使数据分布尽可能均匀;(2)避免对热点数据使用Hash Tag,致使请求分布不均。
下面是使用Hash Tag的一个例子;经过对product加Hash Tag,能够将全部产品信息放到同一个槽中,便于操做。
cluster_node_timeout参数在前面已经初步介绍;它的默认值是15s,影响包括:
(1)影响PING消息接收节点的选择:值越大对延迟容忍度越高,选择的接收节点越少,能够下降带宽,但会下降收敛速度;应根据带宽状况和应用要求进行调整。
(2)影响故障转移的断定和时间:值越大,越不容易误判,但完成转移消耗时间越长;应根据网络情况和应用要求进行调整。
前面提到,只有当16384个槽所有分配完毕时,集群才能上线。这样作是为了保证集群的完整性,但同时也带来了新的问题:当主节点发生故障而故障转移还没有完成,原主节点中的槽不在任何节点中,此时会集群处于下线状态,没法响应客户端的请求。
cluster-require-full-coverage参数能够改变这一设定:若是设置为no,则当槽没有彻底分配时,集群仍能够上线。参数默认值为yes,若是应用对可用性要求较高,能够修改成no,但须要本身保证槽所有分配。
redis-trib.rb提供了众多实用工具:建立集群、增减节点、槽迁移、检查完整性、数据从新平衡等;经过help命令能够查看详细信息。在实践中若是能使用redis-trib.rb工具则尽可能使用,不但方便快捷,还能够大大下降出错几率。
好了,文章就写到这吧,最后推荐一下不错的c/c++ linux服务器高级架构师学习课程,天天晚上八点都会有直播,学习编程的朋友不妨点一下免费报名,上课的时候会有通知,有时间的时候就能够去听听哦