真正说透Redis五种数据结构
Redis持久化之RDB+AOF+混合持久化实战演练
Redis高可用之主从架构
Redis高可用之哨兵架构实战
前面文件介绍了Redis的主从以及哨兵架构的搭建和原理,如需了解的请查看前面的文章,在Redis3.0版本之前,是通过哨兵架构来监控节点状态以及故障转移的,在大部分场景中哨兵架构就已经满足日常场景需要,但是在一些互联网场景下,哨兵架构的下的redis单机性能就会出现瓶颈。在Redis3.0之后,Redis推出了集群的分布式解决方案,主要解决单机性能的瓶颈,本文就针对整合网上以及官网上对Redis集群的原理做一个分享。
单节点并发瓶颈
Redis 集群是一种分布式数据库方案,集群通过分片来进行数据管理,并可以完成主从复制和故障转移功能。
Redis将数据按照槽位分布在不同的节点上,这样保证了每个节点只处理部分数据,即使在分区的情况下,导致某个节点不可用,那也就只影响部分数据的操作,这样就大大提高了系统的高可用和高并发。
我们都知道Redis的数据都是存储在磁盘上的,默认的也是混合持久化方式,Redis会Fork一个子进程完成持久化,如果节点的数据量达到上百GB的时候,这个持久化的过程是非常耗时,并且占用CPU,这种情况下会导致Redis出现卡顿的情况,包括在主从同步时,也是非常慢的,很可能导致超时而同步失败。那么怎么解决呢?
这个时候就无法使用垂直扩展了,不能再加大机器的内存等配置了,所以这个时候就很需要Redis集群架构,通过增加实例来将数据进行水平拆分,将单节点的数据量分摊到每个节点上,这样就能解决上述问题
1、槽位的分配
在创建集群时,Redis 会自动将 16384 个 哈希槽平均分布在集群实例上,也可以手动进行槽位的分配,比如某些机器的配置高,这样就可以分配多一些槽位。Redis是通过CRC16算法对key进行hash运算,然后在对16384进行取模的到真正的槽位。
HASH_SLOT = CRC16(key) mod 16384
切记,当 16384 个槽都分配完全,Redis 集群才能正常工作,如果槽位未分配完,在执行命令时会报如下错
(error) CLUSTERDOWN The cluster is down
2、hash标签
在一些场景中,我们希望一些不同的key可以分布到同一个节点上,这个时候hash标签就派上用场了。
为了实现哈希标签,哈希槽是用另一种不同的方式计算的。基本来说,如果一个键包含一个 “{…}”
这样的模式,只有 { 和 } 之间的字符串会被用来做哈希以获取哈希槽,注意,是第一个{ 和第一个 }
之间的数据参与hash槽位的计算
Redis集群节点间采取gossip协议进行通信,也叫集群总线(端口默认为节点的端口+10000,如6379节点的通信端口为16379),这种协议通信是不需要想zookeeper那样集中式管理元数据。
gossip协议包含多种消息,包括ping,pong,meet,fail等等。
meet
:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信ping
:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等)pong
: 对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新fail
: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
集中式
的优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力。 很多中间件都会借助zookeeper集中式存储元数据。
gossip协议
的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后
集群中各个节点都是通过gossip协议来进行节点通信的,当某个节点无法ping通时,发起ping的节点就会标记此节点故障,然后会告知其他节点此节点的故障信息,如果整个集群中大多数节点都判断此节点故障,则会进行故障转移操作。
从节点满足以下情况才允许参与选举
当一个 Slave 发现自己的主节点进入已下线状态后,从节点将开始对下线的主节点进行故障转移
从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是使用Raft算法算出一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
延迟计算公式
:
DELAY
= 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK
表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举
问题1:客户端又怎么确定访问的数据到底分布在哪个实例上呢?
Redis节点在通过 Gossip 协议与集群中其它节点通信时,会发生槽位对应的节点信息,这样所有节点都知道槽位对应节点地址的信息。
客户端在请求时,Redis服务返回所有的槽位与对应的实例信息,客户端可以将这些信息保存在本地,然后在请求时,客户端根据key计算出对应的槽位信息,这样能准确的将信息发生到对应的实例上,提高命中率和效率。
问题2:在新增节点或者移动槽位后,可能导致之前客户端存的槽位映射不准确,客户端则命中错误的节点
客户端命中错误的节点后,由于集群节点不能代理(proxy)请求,redis服务端会反馈客户端正确的节点信息,重定向分为两种情况 MOVED
和ASK
,详细可以参考官网上的解释
然后再更新本地存储的槽位信息
并且不会更新本地存储的槽位信息
个人的理解:客户端请求服务端,如果数据存在,则直接返回,如果不存在,则判断槽位是否正在迁移中,如果不是,则表示迁移完成,反馈客户端MOVED重定向,客户端收到后会更新本地槽位信息,然后请求新的节点。如果在迁移中,则反馈客户端ASK,客户端收到后不会更新槽位信息,然后会请求新的节点
Redis集群中的节点并不是越多越好,官方给出的建议上线是1000个节点。
主要原因由前面可知,集群间的通信是gossip协议,是无中心化的,在一个有 N 个节点的集群中,每个节点都有 N-1 个流出的 TCP 连接,和 N-1 个流入的连接。 这些 TCP 连接会永久保持,并不是按需创建的。
那么当节点越多,通信的成本就越大,包括选举耗时也会变长。所以合理安排集群节点的数量有利于系统的稳定性
虽然集群有效的将数据分散到不同的实例上,但是对于热点数据,比如促销等活动时,就很容易产生数据落在其中一台机器上,这样就造成了单台机器的压力过大,很容易产生雪崩的情况。数据倾斜分为两种情况
在docker安装集群时,客户端可能会访问节点异常,原因是节点暴露的是容器内的IP,
在配置文件中暴露机器的IP+端口
集群节点IP地址
cluster-announce-ip 121.36.xxx.xxx
节点映射端口
cluster-announce-port 6371
节点故障集群是否可用如果某个主节点没有从节点,那么当它发生故障时,集群将完全处于不可用状态。不过 Redis 也提供了一个参数cluster-require-full-coverage
可以允许部分节点故障,其它节点还可以继续提供对外访问
本文就redis集群中的概念和原理做了分析,在高可用方面,redis提供了主从、哨兵、集群三种方式供大家选择,其复杂和维护成本也是逐渐变大,具体使用请大家结合自己的业务,选择合适的节点部署。