一.哨兵机制
有了主从复制的实现以后,如果想对主服务器进行监控,那么在redis2.6以后提供了一个"哨兵"的机制。顾名思义,哨兵的含义就是监控redis系统的运行状态。可以启动多个哨兵,去监控redis数据库的运行状态。其主要功能有两点:
a、监控所有节点数据库是否在正常运行。
b、master数据库出现故障时,可以自动通过投票机制,从slave节点中选举新的master,实现将从数据库转换为主数据库的自动切换。
一个一主多从的Redis系统中,可以使用多个哨兵进行监控任务以保证系统足够稳健。此时,不仅哨兵会同时监控主数据库和从数据库,哨兵之间也会相互监控。在这里,建议大家哨兵至少部署3个,并且使用奇数个哨兵。
Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:
监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。
哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master.
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置.
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel).
哨兵(sentinel) 的一些设计思路和zookeeper非常类似
监控
sentinel会每秒一次的频率与之前创建了命令连接的实例发送PING,包括主服务器、从服务器和sentinel实例,以此来判断当前实例的状态。down-after-milliseconds时间内PING连接无效,则将该实例视为主观下线。之后该sentinel会向其他监控同一主服务器的sentinel实例询问是否也将该服务器视为主观下线状态,当超过某quorum后将其视为客观下线状态。
当一个主服务器被某sentinel视为客观下线状态后,该sentinel会与其他sentinel协商选出零头sentinel进行故障转移工作。每个发现主服务器进入客观下线的sentinel都可以要求其他sentinel选自己为领头sentinel,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元,每个纪元中只会选择一个领头sentinel。如果所有超过一半的sentinel选举某sentinel领头sentinel。之后该sentinel进行故障转移操作。
如果一个Sentinel为了指定的主服务器故障转移而投票给另一个Sentinel,将会等待一段时间后试图再次故障转移这台主服务器。如果该次失败另一个将尝试,Redis Sentinel保证第一个活性(liveness)属性,如果大多数Sentinel能够对话,如果主服务器下线,最后只会有一个被授权来故障转移。 同时Redis Sentinel也保证安全(safety)属性,每个Sentinel将会使用不同的配置纪元来故障转移同一台主服务器。
故障转移
首先是从主服务器的从服务器中选出一个从服务器作为新的主服务器。选点的依据依次是:网络连接正常->5秒内回复过INFO命令->10*down-after-milliseconds内与主连接过的->从服务器优先级->复制偏移量->运行id较小的。选出之后通过slaveif no ont将该从服务器升为新主服务器。
通过slaveof ip port命令让其他从服务器复制该信主服务器。
最后当旧主重新连接后将其变为新主的从服务器。注意如果客户端与就主服务器分隔在一起,写入的数据在恢复后由于旧主会复制新主的数据会造成数据丢失。
故障转移成功后会通过发布订阅连接广播新的配置信息,其他sentinel收到后依据配置纪元更大来更新主服务器信息。Sentinel保证第二个活性属性:一个可以相互通信的Sentinel集合会统一到一个拥有更高版本号的相同配置上。
我们可以看到哨兵机制是有缺点的:
1.主从服务器的数据要经常进行主从复制,这样造成性能下降。
2.当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不能用的。
二.一致性哈希
- 普通的HASH算法的缺点:
在使用Redis集群的时候,如果直接使用HASH算法hash(key) % length
,当缓存服务器变化时(宕机或新增节点),length字段变化,导致所有缓存的数据需要重新进行HASH运算,这样就导致原来的哪些数据访问不到了。而这段时间如果访问量上升了,容易引起服务器雪崩。因此,引入了一致性哈希 - 一致性哈希:通过对2^32取模的方式,保证了在增加/删除缓存服务器的情况下,其他缓存服务器的缓存仍然可用,从而不引起雪崩问题。
2^32想象成一个圆,就像钟表一样,示意图如下:
圆环的正上方的点代表0,0点的右侧的第一个点代表1,以此类推,2,3,4,..........直到2^32-1,也就是说0的左侧第一个点代表2^32-1,我们把这个由2^32个点组成的圆环成为Hash环。
假设现在我们有4台服务器,通过Hash(服务器的IP地址)% 2^32得到服务器映射到Hash环上的位置。那么我要要缓存的数据对象怎么判断还存在哪些服务器上呢?
一致性哈希算法通过将缓存服务器和被缓存对象都映射到Hash环上以后,从被缓存对象的位置出发,沿顺时针方向遇到的第一个服务器,就是当前对象将要被缓存于的服务器,由于被缓存对象
与服务器hash后的值是固定的,所以在服务器不变的情况下,一个对象必定缓存在一个固定的服务器上,那么,当再次访问这对象时,只要再次使用相同的算法计算即可算出这对象被缓存到哪个服务器上。如下图:
实际应用中一致性哈希还可能出现极端状态如下图:
如果服务器和对象映射成上图这样子,那么被缓存的对象很有可能大部分集中缓存在某一台服务器上。这种现象称为Hash环的偏斜。
那如何解决该现象呢?
我们可以通过虚拟节点的方式解决Hash环的偏移。
如果想要均衡的将缓存分布到这三台服务器上,最好能让这三台服务器的尽量多的,均匀的出现在Hash环上,但是,真实的服务器资源只有3台,那么如何凭空的让他们多起来呢?
做法就是既然没有多余的真正的物理服务器节点,我们就可能将现有的物理节点通过虚拟的方法复制出来,而被复制出来的节点被称为“虚拟节点”,加入虚拟节点后的Hash环如下图:
如果其中一个节点宕机了,一致性Hash能解决容灾问题吗?
明显可以的,我们假设有5台服务器,有a,f,c对象映射在A服务器上,r,t,v对象映射在E服务器上。如下图:
假如此时E服务器宕机了,其他服务器的对象仍然能被命中,因为对象的映射到服务器的位置已经固定了,不会出现因为宕机而让对象找不到。而宕机E上的对象会在下次容灾分配的时候,会把r,t,v这些对象重新分配到就近的服务器上,如下图:
三.Redis官方集群方案 Redis Cluster
Redis在3.0版正式引入了集群这个特性。Redis集群是一个分布式(distributed)、容错(fault-tolerant)的 Redis内存K/V服务, 集群可以使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset),比如Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误。
Redis集群的几个重要特征:
(1). Redis 集群的分片特征在于将键空间分拆了16384个槽位,每一个节点负责其中一些槽位。
(2). Redis提供一定程度的可用性,可以在某个节点宕机或者不可达的情况下继续处理命令.
(3). Redis 集群中不存在中心(central)节点或者代理(proxy)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability)。
Retis Cluster的架构图,如下:
Redis Cluster特点如下:
- 所有的节点相互连接;
- 集群消息通信通过集群总线通信,,集群总线端口大小为客户端服务端口+10000,这个10000是固定值;
- 节点与节点之间通过二进制协议进行通信;
- 客户端和集群节点之间通信和通常一样,通过文本协议进行;
- 集群节点不会代理查询;
Redis Cluster分区实现原理
槽(slot)概念
Redis Cluster中有一个16384长度的槽的概念,他们的编号为0、1、2、3……16382、16383。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster中的每个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb脚本)。这里值得一提的是,在Redis Cluster中,只有Master才拥有槽的所有权,如果是某个Master的slave,这个slave只负责槽的使用,但是没有所有权。Redis Cluster怎么知道哪些槽是由哪些节点负责的呢?某个Master又怎么知道某个槽自己是不是拥有呢?
位序列结构
Master节点维护着一个16384/8字节的位序列,Master节点用bit来标识对于某个槽自己是否拥有。比如对于编号为1的槽,Master只要判断序列的第二位(索引从0开始)是不是为1即可。
如上面的序列,表示当前Master拥有编号为1,134的槽。集群同时还维护着槽到集群节点的映射,是由长度为16384类型为节点的数组实现的,槽编号为数组的下标,数组内容为集群节点,这样就可以很快地通过槽编号找到负责这个槽的节点。位序列这个结构很精巧,即不浪费存储空间,操作起来又很便捷。
故障容忍度
(1)心跳和gossip消息
Redis Cluster持续的交换PING和PONG数据包。这两种数据包的数据结构相同,都包含重要的配置信息,唯一的不同是消息类型字段。PING和PONG数据包统称为心跳数据包。
每个节点在每一秒钟都向一定数量的其它节点发送PING消息,这些节点应该向发送PING的节点回复一个PONG消息。节点会尽可能确保拥有每个其它节点在NOTE_TIMEOUT/2秒时间内的最新信息,否则会发送一个PING消息,以确定与该节点的连接是否正常。
假定一个Cluster有301个节点,NOTE_TIMEOUT为60秒,那么每30秒每个节点至少发送300个PING,即每秒10个PING, 整个Cluster每秒发送10x301=3010个PING。这个数量级的流量不应该会造成网络负担。
(2)故障检测。
Redis Cluster的故障检测用于检测一个master节点何时变得不再有效,即不能提供服务,从而应该让slave节点提升为master节点。如果提升失败,则整个Cluster失效,不再接受客户端的服务请求。
当一个节点A向另外一个节点B发送了PING消息之后,经过NODE_TIMEOUT秒时间之后仍然没有收到PONG应答,则节点A认为节点B失效,节点A将为该节点B设置PFAIL标志。
在 NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT时间内,当Cluster中大多数节点认为节点B失效,即设置PFAIL标志时,这个Cluster认为节点B真的失效了,此时节点A将为节点B设置FAIL标志,并向所有节点发送FAIL消息。
在一些特定情况下,拥有FAIL标志的节点,也可以清除掉FAIL标志。
Redis Cluster故障检测机制最终应该让所有节点都一致同意某个节点处于某个确定的状态。如果发生这样的情况少数节点确信某个节点为FAIL,同时有少数节点确认某个节点为非FAIL,则Redis Cluster最终会处于一个确定的状态:
情况1:最终大多数节点认为该节点FAIL,该节点最终实际为FAIL。
情况2:最终在N x NODE_TIMEOUT时间内,仍然只有少数节点将给节点标记为FAIL,此时最终会清除这个节点的FAIL标志。
Redis投票容错如下图所示:
重定向客户端
文章开始讲到,Redis Cluster并不会代理查询,那么如果客户端访问了一个key并不存在的节点,这个节点是怎么处理的呢?比如我想获取key为msg的值,msg计算出来的槽编号为254,当前节点正好不负责编号为254的槽,那么就会返回客户端下面信息:
GET msg
-MOVED 254 127.0.0.1:6381
表示客户端想要的254槽由运行在IP为127.0.0.1,端口为6381的Master实例服务。如果根据key计算得出的槽恰好由当前节点负责,则当期节点会立即返回结果。这里明确一下,没有代理的Redis Cluster可能会导致客户端两次连接急群中的节点才能找到正确的服务,推荐客户端缓存连接,这样最坏的情况是两次往返通信。
slots配置传播
Redis Cluster采用两种方式进行各个master节点的slots配置信息的传播。所谓slots配置信息,即master负责存储哪几个slots。
(1)心跳消息。
在PING/PONG消息中包含了所负责的slots配置信息。
(2)UPDATE消息。
当一个节点收到PING/PONG消息后,如果发现发送者的世代小于自己的世代,则向其发送UPDATE消息,来更新其有可能已经过时的slots配置信息。如果发现发送者的世代大于自己的世代,则用消息中的slots配置信息更新自己的slots配置信息。
重新分片
Redis Cluster的Resharding是指在Cluster内的节点之间转移slots中的键数据,一个slot之前由某个节点负责,在Resharding之后,可能由另外一个节点负责。
复制迁移
Redis Cluster在节点失效时,可能进行自动的slave节点重新配置,修改了Cluster中部分节点的master-slave复制关系,即复制迁移。
假定场景:
Cluster中有三个master节点:A、B、C。A有1个slave节点A1,B有1个slave节点B1,C有2个slave节点C1和C2。Aj节点失效了,将A1节点提升为master节点。
考虑不进行自动的slave节点的复制迁移:
如果A失效了,则会将唯一的slave节点A1提升为master节点,但是它没有slave节点。这时如果A1节点又失效了,则原来A节点负责的slots将失效,导致整个cluster不能正常工作。
考虑进行自动的slave节点的复制迁移:
如果A节点失效了,将唯一的slave节点A1提升为master节点,由于它没有slave节点,此时发现C节点有2个slave节点,将其中的C2节点重新配置为A1节点的子节点。这时,Cluster中每个master节点至少有1个slave节点。如果A1节点失效,可将C2节点提升为master。这样的结果是提高了整个cluster的可用性。
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点