1背景
前面我们学习了Redis高可用的两种架构模式:主从模式、哨兵模式。解决了我们在Redis实例发生故障时,具备主从自动切换、故障转移的能力,最终保证服务的高可用。但是这些其实远远不够,随着我们业务规模的不断扩展,用户量膨胀,并发量持续提升。原有的主从架构,已经远远达不到我们的需求了,这时候会有一些问题出现,比如:
单机的CPU、内存、连接数、计算力都是有极限的,不能无限制的承载流量的扩增。
超额的请求、大规模的数据计算,导致必然的慢响应。这时候就需要适当的推进架构的演进,来满足发展的需要。
2Cluster模式介绍
2.1什么是Cluster模式
Cluster即集群模式,类似MySQL,Redis集群也是一种分布式数据库方案,集群通过分片(sharding)模式来对数据进行管理,并具备分片间数据复制、故障转移和流量调度的能力。这种分治模式很常见,我们在微服务系列:拆分策略和MySQL系列:分库分表中实践过很多次了。Redis集群的做法是将数据划分为(2的14次方)个哈希槽(slots),如果你有多个实例节点,那么每个实例节点将管理其中一部分的槽位,槽位的信息会存储在各自所归属的节点中。以下图为例,该集群有4个Redis节点,每个节点负责集群中的一部分数据,数据量可以不均匀。比如性能好的实例节点可以多分担一些压力。
一个Redis集群一共有个哈希槽,你可以有1~n个节点来分配这些哈希槽,可以不均匀分配,每个节点可以处理0个到至多个槽点。当个哈希槽都有节点进行管理的时候,集群处于online状态。同样的,如果有一个哈希槽没有被管理到,那么集群处于offline状态。
上面图中4个实例节点组成了一个集群,集群之间的信息通过Gossip协议进行交互,这样就可以在某一节点记录其他节点的哈希槽(slots)的分配情况。
2.2为什么需要Cluster模式
单机的吞吐无法承受持续扩增的流量的时候,最好的办法是从横向(scaleout)和纵向(scaleup)两方面进行扩展,这个我们在MySQL系列和微服务系列的时候已经讨论过了。
纵向扩展(scaleup):将单个实例的硬件资源做提升,比如CPU核数量、内存容量、SSD容量。
横向扩展(scaleout):横向扩增Redis实例数,这样每个节点只负责一部分数据就可以,分担一下压力,典型的分治思维。
那横向扩展和纵向扩展各有什么优缺点呢?
scaleup虽然操作起来比较简易。但是没法解决Redis一些瓶颈问题,比如持久化(如轮式RDB快照还是AOF指令),遇到大数据量的时候,照样效率会很低,响应慢。另外,单台服务机硬件扩容也是有限制的,不可能无限操作。
scaleout更容易扩展,分片的模式可以解决很多问题,包括单一实例节点的硬件扩容限制、成本限制,还可以分摊压力,精细化治理,精细化维护。但是同时也要面临分布式带来的一些问题现实情况下,在面对千万级甚至亿级别的流量的时候,很多大厂都是在千百台的实例节点组成的集群上进行流量调度、服务治理的。所以,使用Cluster模式,是业内广泛采用的模式。
3Cluster实现原理
3.1集群的组群过程
集群是由一个个互相独立的节点(readisnode)组成的,所以刚开始的时候,他们都是隔离,毫无联系的。我们需要通过一些操作,把他们聚集在一起,最终才能组成真正的可协调工作的集群。各个节点的联通是通过CLUSTERMEET命令完成的:CLUSTERMEETipport。具体的做法是其中一个node向另外一个node(指定ip和port)发送CLUSTERMEET命令,这样就可以让两个节点进行握手(handshake操作),握手成功之后,node节点就会将握手另一侧的节点添加到当前节点所在的集群中。这样一步步的将需要聚集的节点都圈入同一个集群中,如下图:
3.2集群数据分片原理
现在的Redis集群分片的做法,主要是使用了官方提供的RedisCluster方案。这种方案就是的核心就是集群的实例节点与哈希槽(slots)之间的划分、映射与管理。下面我们来看看他具体的步骤。
3.2.1哈希槽(slots)的划分
这个前面已经说过了,我们会将整个Redis数据库划分为个哈希槽,你的Redis集群可能有n个实例节点,每个节点可以处理0个到至多个槽点,这些节点把个槽位瓜分完成。而你实际存储的Redis键值信息也必然归属于这个槽的其中一个。slots与RedisKey的映射是通过以下两个步骤完成的:
使用CRC16算法计算键值对信息的Key,会得出一个16bit的值。
将第1步中得到的16bit的值对取模,得到的值会在0~之间,映射到对应到哈希槽中。当然,可能在一些特殊的情况下,你想把某些key固定到某个slot上面,也就是同一个实例节点上。这时候可以用hashtag能力,强制key所归属的槽位等于tag所在的槽位。其实现方式为在key中加个{},例如test_key{1}。使用hashtag后客户端在计算key的crc16时,只计算{}中数据。如果没使用hashtag,客户端会对整个key进行crc16计算。下面演示下hashtag使用:
.0.0.1:clusterkeyslotuser:case{1}(integer).0.0.1:clusterkeyslotuser:favor(integer)1023.0.0.1:clusterkeyslotuser:info{1}(integer)
如上,使用hashtag后会对应到通一个hashslot:中。
3.2.2哈希槽(slots)的映射
一种是初始化的时候均匀分配,使用clustercreate创建,会将个slots平均分配在我们的集群实例上,比如你有n个节点,那每个节点的槽位就是/n个了。另一种是通过CLUSTERMEET命令将node1、node2、ndoe3、node44个节点联通成一个集群,刚联通的时候因为还没分配哈希槽,还是处于offline状态。我们使用clusteraddslots命令来指定。指定的好处就是性能好的实例节点可以多分担一些压力。
可以通过addslots命令指定哈希槽范围,比如下图中,我们哈希槽是这么分配的:实例1管理0~哈希槽,实例2管理~哈希槽,实例3管理~哈希槽,实例4管理~哈希槽。
redis-cli-h..0.1–pclusteraddslots0,redis-cli-h..0.2–pclusteraddslots,redis-cli-h..0.3–pclusteraddslots,redis-cli-h..0.4–pclusteraddslots,
slots和Redis实例之间的映射关系如下:
keytestkey_1和testkey_2经过CRC16计算后再对slots的总个数取模,结果分别匹配到了cache1和cache3上。
3.3数据复制过程和故障转移
3.3.1数据复制
Cluster是具备Master和Slave模式,Redis集群中的每个实例节点都负责一些槽位,比如上图中的四个节点分管了不同的槽位区间。而每个Master至少需要一个Slave节点,Slave节点是通过《Redis系列3:高可用之主从架构》方式同步主节点数据。节点之间保持TCP通信,当Master发生了宕机,RedisCluster自动会将对应的Slave节点选为Master,来继续提供服务。与纯主从模式不同的是,主从节点之间并没有读写分离,Slave只用作Master宕机的高可用备份,所以更合理来说应该是主备模式。如果主节点没有从节点,那么一旦发生故障时,集群将完全处于不可用状态。但也允许配置cluster-require-full-coverage参数,及时部分节点不可用,其他节点正常提供服务,这是为了避免全盘宕机。主从切换之后,故障恢复的主节点,会转化成新主节点的从节点。这种自愈模式对提高可用性非常有帮助。
3.3.2故障检测
一个节点认为某个节点宕机不能说明这个节点真的挂起了,无法提供服务了。只有占据多数的实例节点都认为某个节点挂起了,这时候cluster才进行下线和主从切换的工作。Redis集群的节点采用Gossip协议来广播信息,每个节点都会定期向其他节点发送ping命令,如果接受ping消息的节点在指定时间内没有回复pong,则会认为该节点失联了(PFail),则发送ping的节点就把接受ping的节点标记为主观下线。如果集群半数以上的主节点都将主节点xxx标记为主观下线,则节点xxx将被标记为客观下线,然后向整个集群广播,让其它节点也知道该节点已经下线,并立即对下线的节点进行主从切换。
3.3.3主从故障转移
当一个从节点发现自己正在复制的主节点进入了已下线,则开始对下线主节点进行故障转移,故障转移的步骤如下:
如果只有一个slave节点,则从节点会执行SLAVEOFnoone命令,成为新的主节点。
如果是多个slave节点,则采用选举模式进行,竞选出新的Master集群中设立一个自增计数器,初始值为0,每次执行故障转移选举,计数就会+1。
检测到主节点下线的从节点向集群所有master广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,所有收到消息、并具备投票权的主节点都向这个从节点投票。
如果收到消息、并具备投票权的主节点未投票给其他从节点(只能投一票哦,所以投过了不行),则返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示支持。
参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,如果收集到的选票大于等于(n/2)+1支持,n代表所有具备选举权的master,那么这个从节点就被选举为新主节点。
如果这一轮从节点都没能争取到足够多的票数,则发起再一轮选举(自增计数器+1),直至选出新的master。
新的主节点会撤销所有对已下线主节点的slots指派,并将这些slots全部指派给自己。新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。跟哨兵类似,两者都是基于Raft算法来实现的,流程如图所示:
3.4client访问数据集群的过程
3.4.1定位数据所在节点
我们前面说过了,Redis中的每个实例节点会将自己负责的哈希槽信息通过Gossip协议广播给集群中其他的实例,实现了slots分配信息的扩散。这样的话,每个实例都知道整个集群的哈希槽分配情况以及映射信息。所以客户端想要快捷的连接到服务端,并对某个redis数据进行快捷访问,一般是经过以下步骤:
客户端连接任一实例,获取到slots与实例节点的映射关系,并将该映射关系的信息缓存在本地。
将需要访问的redis信息的key,经过CRC16计算后,再对取模得到对应的Slot索引。
通过slot的位置进一步定位到具体所在的实例,再将请求发送到对应的实例上。下图展示了Redis客户端如何定位数据所在节点:
4总结
哨兵模式已经实现了故障自动转移的能力,但业务规模的不断扩展,用户量膨胀,并发量持续提升,会出现了Redis响应慢的情况。
使用RedisCluster集群,主要解决了大数据量存储导致的各种慢问题,同时也便于横向拓展。在面对千万级甚至亿级别的流量的时候,很多大厂的做法是在千百台的实例节点组成的集群上进行流量调度、服务治理的。
整个Redis数据库划分为个哈希槽,Redis集群可能有n个实例节点,每个节点可以处理0个到至多个槽点,这些节点把个槽位瓜分完成。
Cluster是具备Master和Slave模式,Redis集群中的每个实例节点都负责一些槽位,节点之间保持TCP通信,当Master发生了宕机,RedisCluster自动会将对应的Slave节点选为Master,来继续提供服务。
客户端能够快捷的连接到服务端,主要是将slots与实例节点的映射关系存储在本地,当需要访问的时候,对key进行CRC16计算后,再对取模得到对应的Slot索引,再定位到相应的实例上。实现高效的连接。
作者:
翁智华
出处: