1.引子
说起redis,小伙伴们都很熟悉,用一句话来描述:reids是一个功能丰富,性能高,基于k/v的NoSql数据库
它的功能丰富体现在
- 提供了丰富的数据结构能力:字符串、哈希、列表、集合、有序集合
- 提供了持久化能力:RDB、AOF
- 提供了多种使用姿势:主从复制、哨兵、集群
- 提供了键过期能力,可用于缓存系统实现
- 提供了发布订阅能力,可用于消息系统实现
- 提供了分布式支持能力,可用于实现分布式锁、分布式Id
- 提供了计数能力,可用于计数器系统实现
- 等
它的高性能体现在
- 单线程架构,避免多线程竞争,切换带来的性能损失
- 官方给出了10万量级的QPS,实际应用中打个对折,通常支持到3-5万的QPS
你看到了,这就是redis,不仅功能丰富,且高性能!这也是为什么今天,很多应用中我们都会选择redis作为缓存方案的实现
今天这篇文章,我不准备给你分享redis的细节,它们太多了,比如说客户端命令;比如说数据结构对应的内部编码;比如说RESP协议;比如说持久化等
我们重点来探讨一下主从复制、哨兵、集群的使用姿势。让我们开始吧!
2.案例
这里我们假设在x应用中,选择redis作为缓存方案的实现。我们分别来看一下
- 如果采用主从复制方案,需要考虑哪些点?
- 如果采用哨兵模式,需要考虑哪些点?
- 如果采用集群模式,需要考虑哪些点?
2.1.主从复制
首先来看主从复制,所谓主从复制即明确了两类角色的存在
- 主master
- 从slave
主从复制的逻辑架构,大概是这样的
通常主master负责读写请求;从slave负责备份;如果我们的应用存储的数据量级不大,我们完全可以考虑主从复制的架构方案。因为架构上足够简单,且运维起来也不复杂
但是,我们需要清楚,主从复制方案是带有先天缺陷的!
- 操作请求主要都在主master,哪怕我们通过从slave来分担读压力,但是写压力还是全部在主master,存在写瓶颈
- 存在单点故障风险,如果主master故障,应用方、运维方都需要人工介入干预
- 从slave主要起到备份作用,没有办法分担主master的存储压力,存在存储瓶颈
因此主从复制方案,适合于业务量比较小,且对可用性要求不高的业务系统。但凡这二者不满足其一,我们都不推荐使用主从复制方案
2.2.哨兵模式
主从复制方案,有单点故障的风险,不能满足高可用业务系统。那么redis如何解决高可用的问题呢?答案是哨兵模式。
具体关注哨兵模式前,我们先明确高可用的概念,即在系统建设目标中,有一个很关键的系统指标,叫做可用性或者可靠性。比如说3个9、4个9、5个9的可用性,分别代表什么呢?指的是1年系统故障的时间不能超过的范围,具体
- 3个9: (1-99.9%) x 365 x 24=8.76小时 ,即系统1年可允许最大故障时间是8.76小时
- 4个9: (1-99.99%) x 365 x 24 =0.876小时=52.6分钟 ,即系统1年可允许最大故障时间是52.6分钟
- 5个9: (1-99.999%) x 365 x 24 =5.26分钟 ,即系统1年可允许最大故障时间是5.26分钟
有了可用性、可靠性等可量化的指标后,我们看到了今天应用系统的建设上,高可用是一个很重要的建设指标。那么redis是如何解决高可用的呢?我们也来看一个逻辑架构
我们看到哨兵模式,是在主从复制模式的基础上,增加了一个监控的集群,这个集群的每一个节点都是一个哨兵,它们监控着整个redis数据节点(主master、从slave都是数据节点),一旦发现主master节点发生故障,即自动进行切主、通知应用方等一系列运维动作,来保障服务高可用性。
我们可以举例描述一次主master故障,切换的过程
- 假设哨兵1,某一时刻与主master节点通信异常,哨兵1会认为主master发生了故障,并主观的将master下线(此时我们叫做主观下线)
- 于是哨兵1,会将主master下线的消息,与其它哨兵节点2、3交换意见,是否大家都认为主master发生了故障
- 一旦有超过半数哨兵节点(大多数原则),都认为主master下线了(此时我们叫做客观下线)
- 主master被客观下线后,哨兵集群准备要完成切主,即从slave中选择一个节点,作为新的master节点,其它从slave节点即将从新的master复制数据
- 在切主前,哨兵集群节点之间内部需要完成一个选举,选举一个leader出来,作为此次切主的操作者
- 哨兵节点leader开始切主,将某个slave升级为新的主master,将其它的slave切换到从新的master复制数据。如果旧的master恢复,也将作为slave从新的master复制数据
- 切主完成
你看整个切主过程,不管是应用小伙伴,还是运维同学都毫无感知!这就是哨兵模式的高可用保障。
如果我们的应用存储压力不大、读写请求压力不大,但是对可用性要求很高,这个时候哨兵模式是一个不错的实现方案,毕竟哨兵模式方案的成本也不算高。
另外你需要注意,哨兵模式仅仅能解决高可用问题,它没有为我们解决分布式问题,即数据存储压力、请求压力该如何解决呢?答案是集群模式。
2.3.集群模式
哨兵模式解决了高可用的问题,但是没有解决分布式问题。那么对于分布式问题,其中非常核心的一个因素是分片(数据分片)。
暂时抛开redis,我们来探讨数据分片。一般在存储系统中,数据分片有两种策略供我们选择
- 哈希分片:将数据按照一个的规则,计算哈希值,再与节点求余,确定数据目标存储节点。比如hash(key) % n--->目标存储节点
- 范围分片:将数据按照顺序,进行切分,指定存储到目标节点。比如数据范围1-1000,则将1-500存储节点1;将501-1000存储到节点2
两种数据分片策略,哈希各有优缺点
- 哈希分片优点是数据分步均匀,避免热点数据问题;缺点是扩容缩容麻烦,需要重新计算分片迁移数据
- 范围分片优点是扩容缩容,不需要迁移数据;缺点是有热点数据问题,比如某个区域大量活跃用户,即该区域则是热点
因此在实际应用中,我们需要结合实际的业务需要来权衡应用哪一种分片策略。在redis集群方案中,选择了哈希分片的策略,但是redis对哈希分片策略进行了改进
- 节点取余哈希分片,不利于扩容缩容,需要迁移数据
- 一致性哈希分片,作为节点取余哈希的改进方案。它的原理是让节点形成一个哈希环,读写数据请求查询节点时,根据key计算hash值,然后顺时针找到第一个大于等于该hash值的节点,即目标节点
一致性哈希带来的好处是,当扩容缩容的时候,只影响哈希环中相邻的节点,最大化降低了数据迁移的影响。但是一致性哈希依然存在一些问题
- 扩容缩容节点,会造成哈希环中部分数据无法命中,需要人工介入处理
- 如果集群节点太少,节点变化会大范围影响哈希环中数据映射,不适合少量数据节点分布式方案
鉴于一致性哈希分片存在的问题,redis在集群方案中,进一步做了改进,通过哈希槽。什么是哈希槽呢?你可以这么去理解
- 哈希槽实现了与数据的映射
- 同时实现了与节点的映射,
- 即哈希槽是数据,与节点之间增加的一个中间层,用于解耦数据,与节点的映射,你看这里再次体现了我们常说的架构哲学:任何软件架构的问题,都可以通过增加一个中间层从而得到解决
- 因此在redis集群方案中,哈希槽是基本操作单位
- 有了哈希槽,当扩容缩容的时候,只需要分配迁移哈希槽即可
理解了redis集群的数据分片策略,我们最后来看一下集群的逻辑架构,这里你需要注意,在redis集群中,总共提供了16384个哈希槽,即编号从0-16383
最后我们来总结一下,redis通过集群方案,解决了
- 读写请求压力分担,负载均衡,弹性扩缩容
- 突破了单机节点数据存储瓶颈,将数据通过hash槽实现了数据分片
- 单机节点单点故障,通过集群实现高可用
当然凡事都是有代价的,redis集群方案相比主从复制、哨兵模式来说,是成本更高的架构方案。还是那句话,在实际应用中我们需要结合具体业务需要,来权衡应用哪一种方案,请记住这句话:没有最好的方案,只有更适合的方案!