Redis Cluster是Redis提供的一个分布式解决方案,在3.0推出。Redis Cluster可以自动将数据分片分布到不同的master节点上,同时提供了高可用的支持,当某个master节点挂了之后,整个集群还是可以正常工作。
1、为什么要用Redis Cluster?
前面十三章讲解了Redis中的哨兵模式。了解了Redis基于读写分离实现的主从架构。同时也知道了当Redis的master节点发生故障时,Sentinel是如何执行故障转移的。
的确,在数据上有从节点做副本数据备份;可用性上,有Sentinel保证master发生故障时,自动执行故障转移。
那么我们就会有疑问,为什么还需要Redis Cluster呢?
首先,Redis Sentinel实际上就是基于主从复制,在主从复制中,从节点的数据完全来源于master节点。
那么,假设现在master节点的内存只有4G,那么slave节点最多也就只能存储4G的数据。而且在前面第十三章中也介绍过,在主从复制的架构中读写是分离,也就是说我们可以横向增加slave节点的数量来提升Redis的读并发能力,但是写能力和存储能力是无法扩展的,就只能是master节点的承载上限。
因此,当我们只存储4G的数据时,基于主从复制和Sentinel的高可用架构完全没得问题。但是当我们的数据量达到16G、64G、1TB呢?在现在互联网的业务中,只要公司的体量大,我感觉必然会面临海量数据缓存问题。
这就是为什么要引入Redis Cluster的原因。
2、Redis Cluster是什么
Redis Cluster我们可以很简单的理解为n个主从架构一起对外提供服务。Redis Cluster要求最少3个master才能组成一个集群,同时每个master至少有一个slave节点。
这样一来,如果一个主从能够存储32G数据,那么2个主从就可以存储64G的数据。如果有更大量的数据,只需要加相应数量的主从即可。
在主从架构中,我们知道可以通过增加slave节点的数量来提供Redis的读请求并发能力。那么Redis Cluster
是如何做的呢?虽然每个master节点都挂了至少一个slave节点,但是slave节点只是做数据的备份作用,所有的读写请求都由master节点提供。
3、节点负载均衡
从上面我们知道,在Redis Cluster
中只有master节点对外提供读写能力,且有多个master,每个master上的存储的数据都不一样。那么Redis Cluster
是如何知道哪个数据存储到哪个master上的呢?
3.1、哈希算法
一般的负载均衡算法,基本上会采用哈希算法。
首先对key计算出一个hash值,然后用hash值对master的数据量取模。由此就可以将key均匀的分布到每个master节点上。这就是简单的哈希算法的实现。但是Redis Cluster
并没有采用这种实现方式。而是采用了一个类一致性哈希算法的实现方式。
对于为什么没有采用哈希算法原因是:假如此时有一台master节点挂掉了,那么此时会导致Redis中的所有缓存失效(基本上所有数据都查不到)。
那么为什么呢?假如现在有3台master节点,那么之前的哈希算法应该是hash % 3,如果此时有一台master节点挂掉了,那么此时的哈希算法就应该是hash % 2。由于取模的基数不一样了,那么势必会影响的之前存储的所有数据。
3.2、一致性哈希算法
我们上面说到的哈希算法,是对master节点数量进行取模。而一致性哈希算法,是对232 取模,也就是值的范围在[0-232-1]。一致性哈希算法将其范围抽象成一个圆环,使用CRC16算法计算出来的哈希值会落到圆环的某个地方。
而且,我们的Redis实例也分布在圆环上,我们在圆环上按照顺时针的顺序找到第一个Redis实例,那么这个key存储的就是在这个实例上。
举个例子:假设我们有A、B、C三个实例按照如图的方式分布在圆环上,此时计算出来的hash值对应在D的位置,那么我们按照顺时针的顺序,就能够找到这个key应该分配的Redis实例B。同理计算出来的位置在E,那么对应的Redis实例就是A。
即使这个时候Redis实例B挂掉了也不会影响到A、C两个实例。
假如此时B节点挂掉了,那之前计算出来的位置D的key,按照顺时针顺序找到节点C。相当于把节点B的流量转移到节点C上,原来节点A、C的流量没有影响。
这就是一致性哈希算法,能够在我们后续删除或者添加节点的时候,不影响其他节点。
3.3、一致性哈希算法的虚拟节点机制
但是一致性哈希算法还是有点问题,例如当们的Redis
节点按照如下分布时:
如果按照Redis节点上图分布时,明显数据落在节点A上的几率更大,其次落到节点C的几率最小。这样一来导致整个集群的数据存储不平衡,A、B节点的负载较高,节点C的资源利用不足,所以为了解决这个问题,引入了虚拟节点机制。
在圆环中增加了对应节点的虚拟节点,然后完成了虚拟节点到真实节点的映射。假设现在计算出来的结果是位置D,那么按照顺时针顺序,我们找到的第一个节点就是虚拟节点C#1,由于有虚拟节点到真实节点的映射关系,所以数据最终会落到真实节点C上。
通过增加虚拟节点的方式,使ABC三个节点在圆环上分布更加均匀,平均了落在每个节点上的概率。这样就解决了上面提到的节点分布不均匀导致的数据分布不均匀的问题。这就是一致性哈希算法的虚拟节点机制。
4、Redis Cluster采用的算法
Redis Cluster 采用的是类一致性哈希算法。
一致性哈希算法是对232取模,而Redis Cluster则是对214(也就是16384)取模。Redis Cluster将自己分成了16384个Slot(槽位)。通过CRC16计算出来的哈希值,会对16384取模,取模之后得到的值就是对应的槽位,然后每个redis节点都会负责处理一部分的槽位,就像下标所示:
节点 | 处理槽位 |
---|---|
A | 0-5000 |
B | 5001-10000 |
C | 10001-16383 |
每个Redis实例都会维护一份**slot-Redis节点**的映射关系,但是如果你在节点A上设置了某个key,但是通过CRC16计算的槽位是节点B维护的,那么就会提示你去节点B上进行操作。
5、Redis Cluster如何做到高可用?
现在我们想一个问题:如果Redis Cluster中的某个master节点挂了,它是如何保证集群自身的高可用的?如果我们想在集群里扩容节点,新扩容的节点它应该负责哪些槽位?
5.1、集群如何扩容?
Redis Cluster可以很方便的横向扩容,那当新的节点加入进来时,它是如何获取对应的slot的呢?
答案是通过**reshard(重新分片)**来实现,reshard可以将已经分配给某个节点的任意数量的slot迁移给另一个节点。在Redis内部是通过redis-trib负责执行的。可以理解为Redis其实已经封装好了所有命令,而redis-trib负责向获取slot节点和被转移slot的节点发送命令来实现reshard。
假设我们想集群中加入一个新节点D,而此时集群内已经有A、B、C三个节点。
此时redis-trib会向A、B、C三个节点发送迁移出槽位的请求,同时会向D发送准备导入槽位的请求,做好准备之后A、B、C这三个节点就开始执行迁移,将对应的slot的所有键值迁移到目标节点D。最后redis-trib会向集群中的所有主节点发送槽位变更信息。
5.2、高可用故障转移
Redis Cluster中保证集群高可用的思路和Redis Sentinel如出一辙。
简单来说,针对节点A,如果某个节点认为节点A挂了,那么此时就是主观宕机,而如果集群内超过半数的节点都认为节点A挂了,那么此时A就会标记为客观宕机。
一旦节点A被标记为客观宕机,那么集群就会开始故障转移。其余正常运行的master节点会进行投票选举,从节点A的slave节点中选出一个,将其切换为新的master节点对外提供服务。当某个slave节点获得超过半数的master节点的投票,就成功当选。
当选成功后,新的master节点会执行slaveof no one
来让自己停止复制节点A,使自己成为master节点。然后将A节点负责的slot,全部转移给自己,然后向集群发送PONG消息来广播自己的最新状态。
按照一致性哈希算法的思想,如果某个节点挂了,那么就会沿着那个圆环,按照顺时针找到遇到的第一个Redis实例。
而对于Redis Cluster,某个key它其实是不用关心它最终是去哪个Redis实例,它只要关心自己属于哪个slot,不论你节点怎么迁移,最终还是只需要找到对应的slot即可,然后在找到slot关联的节点。
6、gossip协议
gossip:留言、八卦、小道消息。
gossip协议:就是Redis Cluster各个节点之间交换数据、通信所采用的一种协议。
gossip在最初提出是为了解决分布式数据库中,各个副本节点的数据同步问题。但是随着技术的发展,gossip后续也被广泛用与信息扩散、故障检测等等。
Redis Cluster就是用gossip来实现自身的信息扩散的。
很简单,就像图里那样,每个Redis节点每秒都会向其他节点发送PING,然后被PING的节点会回一个PONG。
6.1、gossip协议消息类型
Redis Cluster中,节点之间的消息类型分为5种,分别是MEET、PING、PONG、FAIL和PUBLISH。
消息类型 | 消息内容 |
---|---|
MEET | 给某个节点发送MEET消息,请求接收消息的节点加入到集群。(新的节点加到集群) |
PING | 每隔一秒钟,选择5个最久没有通信的节点,发送PING消息,检测对应的节点是否在线;同时还有一种策略:如果某个节点的通信延迟大于cluster-node-time 的值的一半,就会立即给该节点发送PING消息,避免数据交换延迟太久。 |
PONG | 当节点收到MEER或者PING消息后,会回一个PONG消息给对方,代表自己收到了MEET或者PING消息。同时,节点也可以主动发送PONG消息向集群中广播自己的信息,让其他节点获取到自己的最新消息。就像完成故障转移之后的新的master向集群发送PONG消息一样。 |
FAIL | 用于广播自己的对某个节点的宕机判断,假设当前节点对节点A判断为宕机,那么就会立即向Redis Cluster广播自己对于节点A的判断,所有收到消息的节点就会对A节点进行标记。 |
PUBLISH | 用于向指定的Channel发送消息,某个节点收到PUBLISH消息之后会直接在集群内广播,这样一来,客户端不论连接到任何节点都能订阅这个Channel。 |
6.2、gossip的优劣
优点 | |
---|---|
扩展性 | 网络可以允许节点的任意增加和减少,新增加的节点的状态最终会与其他节点一致。 |
容错性 | 由于每个节点都持有一份完整的元数据,所以任何节点宕机都不会影响gossip的运行。 |
健壮性 | 与容错性类似,由于所有节点都持有数据,地位平等,是一个去中心化的设计。任何节点都不会影响到服务的运行。 |
最终一致性 | 当有新消息需要传递时,消息可以快速的发送到所有节点,让所有节点都拥有最新的数据。 |
gossip可以在O(logN)轮就可以将信息传播到所有节点,为什么是O(logN)呢?因为每次PING,**当前节点都会带上自己的信息外加整个Cluster的1/10数量的节点信息**,一起发送出去。
7、总结
总的来说,Redis Cluster相当于是把Redis的主从架构和Sentinel继承在一起,从Reids Cluster的高可用机制、判断故障转移以及执行故障转移的过程,都和主从、Sentinel相关。这也就是为什么说,主从是Redis高可用的基石。