08 | 哨兵集群:哨兵挂了,主从库还能切换吗?
1、引言
哨兵机制,它可以实现主从库的自动切换。通过部署多个实例,就形成了一个哨兵集群。哨兵集群中的多个实例共同判断,可以降低对主库下线的误判率。
问题:如果哨兵挂掉了,还能否自由切换主从库?--哨兵集群
哨兵只和主库连接起来,哨兵之间不知道彼此之间的地址
2、基于 pub/sub(发布 / 订阅)机制的哨兵集群组成
哨兵之间的相互发现,要归功于Redis的 pub/sub 机制
哨兵只要和主库连接起来,就可以在主库上发布消息了,比如发布自己的IP+端口号(连接信息)。同时,哨兵也可以从主库订阅消息,获取其他哨兵的连接信息。当多个哨兵和主库连接起来,就可以彼此知道彼此的连接信息。
除了哨兵能发布/订阅消息,我们自己写的应用程序也能发布/订阅消息,redis为了区分不同应用的消息,会对消息加频道进行区分,也就是消息类别。
当消息类别相同时,它们就属于同一个频道。反之,就属于不同的频道。只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。
哨兵除了彼此之间建立起连接形成集群之外,还需要和从库建立起连接,目的是为了切换主库后通知从库。
问题1:哨兵怎么知道从库的IP地址和端口号的?
这是由哨兵向主库发送INFO命令来完成的。
哨兵 2 给主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵。接着,哨兵就可以根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续地对从库进行监控。哨兵 1和 3 可以通过相同的方法和从库建立连接。
总结起来:通过 pub/sub 机制,哨兵之间可以组成集群,同时,哨兵又通过 INFO 命令,获得了从库连接信息,也能和从库建立连接,并进行监控了。
问题2:哨兵不能只和主从库连接,还要向客户端同步新的主库信息,如何在客户端通过监控了解哨兵进行主从切换的过程呢?比如,主从切换到哪一步?
这里我们还需要依赖 pub/sub 发布/订阅 机制。
3、基于 pub/sub 机制的客户端事件通知
本质来说,哨兵就是运行在特定模式下的Redis实例,只不过它不服务请求操作,只是完成监控、选主、通知的任务。
所以每个哨兵也是提供 sub/pub 的机制的,客户端可以从哨兵订阅服务。哨兵提供的消息订阅的频道有很多,包括了主从切换过程中的不同关键事件。
其中几个重要的频道汇总,涉及几个关键事件,包括主库下线判断、新主库选定、从库重新配置。
知道这些频道,就可以让客户端从哨兵这里订阅消息了。
具体的操作步骤是,客户端读取哨兵的配置文件后,可以获得哨兵的地址和端口,和哨兵建立网络连接。然后,我们可以在客户端执行订阅命令,来获取不同的事件消息。
比如:
--1、订阅所有实例进入客观下线状态的事件
SUBSCRIBE +odown
--2、订阅所有事件
PSUBSCRIBE *
-- 3、这个事件表示主库已经切换了,新主库的 IP 地址和端口信息已经有了。这个时候,客户端就可以用这里面的新主库地址和端口进行通信了
switch-master
总结一下 sub/pub 机制:
有了 pub/sub 机制,哨兵和哨兵之间、哨兵和从库之间、哨兵和客户端之间就都能建立起连接再加上我们上节课介绍主库下线判断和选主依据,哨兵集群的监控、选主和通知三个任务就基本可以正常工作了
但是,问题:主库故障以后,哨兵集群有多个实例,那怎么确定由哪个哨兵来进行实际的主从切换呢?
4、由哪个哨兵执行主从切换?
确定由哪个哨兵执行主从切换的过程,和主库“客观下线”的过程类似,也是一个“投票仲裁”的过程。
客观下线具体过程:
任何一个实例只要自身判断主库“主观下线”后,就会给其他实例发送 is-master-downby-addr 命令。
然后其他实例会根据自己与主库的连接状态,作出Y或N的回复
当有一半的哨兵(这个所需的赞成票数是通过哨兵配置文件中的 quorum 配置项设定的,5个哨兵就可以配制成3)判断主库主管下线,就可以标记主库客观下线了,此时,
这个哨兵就可以再给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵
进行投票。这个投票过程称为“Leader 选举”。最终执行主从切换是由这个Leader执行的。
在投票的过程中,任何一个想成为Leader的哨兵,要满足2个条件,
1)、拿到半数以上的赞成票
2)、拿到的票数的同时,还需要大于等于哨兵配置文件中的 quorum 值
以3个哨兵为例,假设哨兵配置文件中quorum值配置为2,那么任何一个想成为Leader的哨兵只需要得到2票即可
如果S3没有拿到2票,这轮就不会产生新的Leader,哨兵集群会等待一段时间(也就是哨兵故障转移超时时间的 2 倍),再重新选举。等待网络拥塞好转,成功概率会增加。
注意一点:如果哨兵集群只有两个哨兵,此时一个哨兵想成为Leader,必须获得两票,所以如果有哨兵挂掉,集群是无法进行主从库切换的,所以,通常我们至少会配置 3 个哨兵实例。
5、小结
支持哨兵集群的这些关键机制:
1)、基于 pub/sub 机制的哨兵集群组成过程;
2)、基于 INFO 命令的从库列表,这可以帮助哨兵和从库建立连接;
3)、基于哨兵自身的 pub/sub 功能,这实现了客户端和哨兵之间的事件通知。
对于主从切换,并不是哪个哨兵想切换就切换的,需要哨兵集群在判断了主库“客观下线”后,经过投票仲裁,选举一个 Leader 出来,由它负责实际的主从切换,即由它来完成新主库的选择以及通知从库与客户端
经验:要保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值 down-after-milliseconds
6、每课一问
假设有一个 Redis 集群,是“一主四从”,同时配置了包含 5 个哨兵实例的集群,
quorum 值设为 2。在运行过程中,如果有 3 个哨兵实例都发生故障了,此时,Redis 主
库如果有故障,还能正确地判断主库“客观下线”吗?如果可以的话,还能进行主从库自
动切换吗?此外,哨兵实例是不是越多越好呢?如果同时调大 down-after-milliseconds
值,对减少误判是不是也有好处呢?
1、哨兵集群可以判定主库“主观下线”。由于quorum=2,所以当一个哨兵判断主库“主观下线”后,询问另外一个哨兵后也会得到同样的结果,2个哨兵都判定“主观下线”,达到了quorum的值,因此,哨兵集群可以判定主库为“客观下线”。
2、但哨兵不能完成主从切换。哨兵标记主库“客观下线后”,在选举“哨兵领导者”时,一个哨兵必须拿到超过多数的选票(5/2+1=3票)。但目前只有2个哨兵活着,无论怎么投票,一个哨兵最多只能拿到2票,永远无法达到多数选票的结果。
3、并不是,哨兵在判定“主观下线”和选举“哨兵领导者”时,都需要和其他节点进行通信,交换信息,哨兵实例越多,通信的次数也就越多,而且部署多个哨兵时,会分布在不同机器上,节点越多带来的机器故障风险也会越大,这些问题都会影响到哨兵的通信和选举,出问题时也就意味着选举时间会变长,切换主从的时间变久。
4、是有好处的,适当调大down-after-milliseconds值,当哨兵与主库之间网络存在短时波动时,可以降低误判的概率。但是调大down-after-milliseconds值也意味着主从切换的时间会变长,对业务的影响时间越久,我们需要根据实际场景进行权衡,设置合理的阈值。
09 | 切片集群:数据增多了,是该加内存还是加实例?
1、数据切片使用场景介绍
需求:5000w个键值对,每对512B,为快速部署,采用云主机来运行Redis实例,问:如何选择云主机的内存容量?
方案一:这些健值对大约占用5000*512约等于25G,所以选择一台32G内存的云主机来部署 Redis。
剩余7G可以保证系统正常运行。同时采用RDB持久化,保证故障恢复。
使用过程中,redis相应有时特别慢,INFO命令查看Redis 的 latest_fork_usec 指标值(表示最近一次 fork 的耗时)显示这个耗时非常高,达到秒级别
这跟Redis持久化有关系,RDB时fork子线程时会阻塞主线程,这个fork操作跟Redis的数据量有直接关系,数据量越大阻塞事件越长,这导致Redis程序变慢了。
方案一行不通。
方案二:采用Redis 的切片集群(也叫分片集群),组建比较麻烦,但是可以保存大量数据,而且对主线程阻塞影响比较小。
切片集群:指启动多个Redis实例组成一个集群,然后按照一定的规则,把收到的数据切分成多份,每一份用一个实例保存起来。
这样在切片集群中,实例在为5G数据生成RDB时,数据量就小了许多,fork子线程时不会阻塞主线程很长时间。
采用切片集群,既能保存25GB 数据,又避免了 fork 子进程阻塞主线程而导致的响应突然变慢
实际应用Redis时,数据量会随着业务增大而增大,而切片集群很好的解决了这个问题
2、如何保存更多数据
在1中,使用了大内存云主机和切片集群两种方法保存了25G数据。
实际上,这两种方法分别对应着 Redis 应对数据量增多的两种方案:纵向扩展(scale up)和横向扩展(scale out)。
纵向扩展:升级单个Redis实例的资源配置,包括增加内存容量、增加磁盘容量,使用更高配置的CPU。
横向扩展:横向增加当前的Redis实例。
ps解释上图:
1)、纵向:原来实例是8G内存+50G硬盘,扩展后实例是24G内存+150G硬盘
2)、横向:由一个8G内存+50G硬盘增加到三个
两种扩展的优缺点:
1)、纵向扩展:
优点:实施起来简单、直接
缺点:两方面。一方面RDB在fork子线程时可能会阻塞主线程,另一方面会受到硬件成本的限制
2)、横向扩展:相对纵向扩展是扩展性更好的方案
优点:增加Redis实例就好,不用考虑硬件成本
在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。
缺点:不过横向扩展带来两个问题:
A、数据切片后,在多个实例之间如何分布?
B、客户端怎么确定想要访问的数据在哪个实例上?
3、数据切片和实例的对应分布关系
切片集群和Redis Cluster 方案的区别和联系
Redis Cluster 的方案,用于实现切片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。
Redis Cluster 方案采用哈希槽(Hash Slot),来处理数据和实例之间的映射关系
在 Redis Cluster 方案中,一个切片集群共有 16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中。
具体的映射过程分为两大步:首先根据键值对的 key,按照CRC16 算法计算一个 16 bit的值;然后,再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
CRC16的算法原理:
1. 根据CRC16的标准选择初值CRCIn的值;
2. 将数据的第一个字节与CRCIn高8位异或;
3. 判断最高位,若该位为 0 左移一位,若为 1 左移一位再与多项式Hex码异或;
4. 重复3直至8位全部移位计算结束;
5. 重复将所有输入数据操作完成以上步骤,所得16位数即16位CRC校验码。
这些哈希槽又是如何被映射到具体的 Redis 实例上的呢?
在部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群,此时,Redis会自动把这些槽平均分布在集群实例上。
例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。
也可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用cluster addslots 命令,指定每个实例上的哈希槽个数。方便处理各个实例内存等大小不一致。
ps:五个hash槽,三个实例,通过以下命令建立手动联系
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4
key1 和 key2 计算完 CRC16 值后,对哈希槽总个数 5 取模,再根据各自的模数结果,就可以被映射到对应的实例 1 和实例 3 上。
另外:在手动分配哈希槽时,需要把 16384 个槽都分配完,否则Redis 集群无法正常工作!
4、客户端如何定位数据?
客户端请求数据-->通过计算-->hash槽位置-->对应实例
一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端
在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。
但是,Redis实例会把自己的hash槽信息发给和它相连的其他实例,来完成hash槽分配信息的扩散。这样,客户端就可以保证在访问任何一个实例时,都能获得所有的哈希槽信息。
客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。
在集群中,实例和Hash槽的对应关系不是一成不变的,最常见的变化有两种:
1)、集群中,实例有新增或者删除,Redis需要重新分配Hash槽
2)、为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍
变化之后,实例之间还可以通过相互传递消息,获得最新的哈希槽分配信息,但是客户端还无法感知,还会按照之前计算的hash槽来匹配实例;
这是我, Redis Cluster 方案提供了重定向机制,当客户端请求到之前的实例时,该实例会进行响应,返回正确实例的访问地址(ip+端口),客户端会直接和新的实例进行通信,并且还会更新本地缓存(MOVED命令)。
GET hello:key
(error) MOVED 13320 172.16.19.5:6379
这个命令表示,请求的健值对在hash槽13320,实际在172.16.19.5:6379上。
这里需要注意的是,客户端发送请求时,hash槽中只有部分数据迁移到了另一个实例,还有部分数据没有迁移,这种情况下,客户端就会收到一条 ASK 报错信息;
GET hello:key
(error) ASK 13320 172.16.19.5:6379
这个ASK命令表示,客户端请求的键值对所在的哈希槽 13320,在172.16.19.5 这个实例上,但是这个哈希槽正在迁移。
ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。
此时,客户端需要先给 172.16.19.5这个实例发送一个 ASKING 命令。这个命令的意思是,让这个实例允许执行客户端接下来发送的命令。然后,客户端再向这个实例发送 GET 命令,以读取数据。
ps:Slot 2 正在从实例 2 往实例 3 迁移,key1 和 key2 已经迁移过去,
key3 和key4 还在实例 2。客户端向实例 2 请求 key2 后,就会收到实例 2 返回的 ASK 命令。
和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息。
5、小结
1)、切片集群保存大量数据
2)、基于哈希槽的数据分布机制
3)、客户端定位键值对
应对数据量扩容时,虽然增加内存这种纵向扩展的方法简单直接,但是会造成数据库的
内存过大,导致性能变慢。Redis 切片集群提供了横向扩展的模式,也就是使用多个实
例,并给每个实例配置一定数量的哈希槽,数据可以通过键的哈希值映射到哈希槽,再通
过哈希槽分散保存到不同的实例上。这样做的好处是扩展性好,不管有多少数据,切片集
群都能应对。
集群的实例增减,或者是为了实现负载均衡而进行的数据重新分布,会导致哈希槽
和实例的映射关系发生变化,客户端发送请求时,会收到命令执行报错信息。了解了
MOVED 和 ASK 命令。
6、每课一问
Redis Cluster 方案通过哈希槽的方式把键值对分配到不同的实例上,这个过程需要对键值对的 key 做 CRC 计算,然后再和哈希槽做映射,这样做有什么好处吗?如果用一个表直接把键值对和实例的对应关系记录下来(例如键值对 1 在实例 2 上,键值对 2 在实例 1 上),这样就不用计算 key 和哈希槽的对应关系了,只用查表就行了,Redis 为什么不这么做呢?
1).让key在多个实例上分布更均匀
2).需要rehash的时候,还要去修改这个对应关系表,代价有点大
3).存在表里,key的数量太大,表的维护是个问题