Redis分布式篇

2021-07-14 15:28:04 浏览数 (1)

Redis分布式篇

为什么需要 Redis 集群

为什么需要集群?

性能

Redis 本身的 QPS 已经很高了,但是如果在一些并发量非常高的情况下,性能还是 会受到影响。这个时候我们希望有更多的 Redis 服务来完成工作

扩展

第二个是出于存储的考虑。因为 Redis 所有的数据都放在内存中,如果数据量大, 很容易受到硬件的限制。升级硬件收效和成本比太低,所以我们需要有一种横向扩展的 方法。

可用性

第三个是可用性和安全的问题。如果只有一个 Redis 服务,一旦服务宕机,那么所有的客户端都无法访问,会对业务造成很大的影响。另一个,如果硬件发生故障,而单 机的数据无法恢复的话,带来的影响也是灾难性的。

主从复制(replication)

Redis 的主从复制分为两类,一种叫全量复制,就是一个节点第一次连接到 master节点,需要全部的数据。第二种叫做增量复制,比如之前已经连接到 master 节点,但是中间网络断开,或者 slave 节点宕机了,缺了一部分的数据。

全量复制

连接阶段

  1. slave节点启动时(或者执行slaveof命令时),会在自己本地保存master节点的信息,包括 master node 的 host 和 ip。
  2. slave 节点内部有个定时任务 replicationCron,每隔1秒钟检查是否有新的 master node 要连接和复制。

如果发现有 master节点,就跟 master节点建立连接。如果连接成功,从节点就为连接建立一个专门处理复制工作的文件事件处理器负责后续的复制工作。为了让主节点感知到 slave 节点的存活,slave 节点定时会给主节点发送 ping 请求。 建立连接以后,就可以同步数据了,这里也分成两个阶段。 数据同步阶段 如果是新加入的slave节点,那就需要全量复制。master通过 bgsave 命令在本地生成一份 RDB 快照,将RDB 快照文件发给 slave节点(如果超时会重连,可以调大 repl-timeout 的值)。 问题∶ 如果 slave 节点自己本来有数据怎么办? slave 节点首先需要清除自己的旧数据,然后用 RDB 文件加载数据。 问题∶master 节点生成 RDB 期间,接收到的写命令怎么处理? 开始生成 RDB文件时,master 会把所有新的写命令缓存在内存中。在 slave节点保存了RDB之后,再将新的写命令复制给 slave节点。(跟 AOF重写 rewrite期间接收到的命令的处理思路是一样的) 第一次全量同步完了,主从已经保持一致了,后面就是持续把接收到的命令发送给 slave 节点。 命令传播阶段 master node 持续把写命令,异步复制给 slave node。 总结起来非常地简单,前面用 RDB文件,后面把命令发给 slave 节点,就实现了主从复制。 注意,一般情况下我们不会用 Redis 做读写分离,因为 Redis 的吞吐量已经够高了,做集群分片之后并发的问题更少,所以不需要考虑主从延迟的问题。

增量复制

如果从节点有一段时间断开了与主节点的连接,是不是要把原来的数据全部清空,重新全量复制一遍?这样效率太低了吧? 如果可以增量复制,怎么知道上次复制到哪里? slave 通过 master_repl_offset 记录的偏移量 Redis 主从复制解决了数据备份和一部分性能的问题,但是没有解决高可用的问题。

哨兵Sentinel

Redis 的高可用是通过哨兵 Sentinel来保证的。它的思路就是通过运行监控服务器来保证服务的可用性。 我们会启动奇数个的 Sentinel 的服务(通过 src/redis-sentinel)。 它本质上只是一个运行在特殊模式之下的Redis。Sentinel通过info 命令得到被监听 Redis 机器的 master,slave 等信息。

为了保证监控服务器的可用性,我们会对 Sentinel做集群的部署。Sentinel既监控所有的 Redis 服务,Sentinel 之间也相互监控。 注意∶ Sentinel本身没有主从之分,地位是平等的,只有Redis 服务节点有主从之分。 这里就有个问题了,Sentinel 唯一的联系,就是他们监控相同的 master,那一个 Sentinel 节点是怎么知道其他的 Sentinle 节点存在的呢? 因为 Sentinel 是一个特殊状态的 Redis 节点,它也有发布订阅的功能。 哨兵上线时,给所有的 Reids节点(master/slave)的名字为 _sentinel_∶hello的 channle 发送消息。 每个哨兵都订阅了所有Reids 节点名字为_sentinel_:hello 的channle,所以能互相感知对方的存在,而进行监控。 Sentinel和Sentinel集群最大的作用就是管理 Redis 节点服务状态,还有切换主从。

服务下线

第一个问题,Sentinel 是怎么知道 master 节点挂了? Sentinel默认以每秒钟1次的频率向 Redis 服务节点发送PING命令。如果在指定时间内(默认是 30 秒)没有收到有效回复,Sentinel 会将该服务器标记为下线(主观下线)。 但是,只有你发现 master下线,并不代表 master 真的下线了。也有可能是你自己的网络出问题了。所以,这个时候第一个发现 master下线的 Sentinel节点会继续询问其他的 Sentinel节点,确认这个节点是否下线,如果多数 Sentinel节点都认为 master下线,master 才真正确认被下线(客观下线)。 确定 master 下线之后,就需要重新选举 master。

故障转移

Redis 的选举和故障转移都是由 Sentinel完成的。问题又来了,既然有这么多的 Sentinel 节点,由谁来做故障转移的事情呢? 故障转移流程的第一步就是在 Sentinel集群选择一个Leader,由Leader完成故障转移流程。Sentinle 通过 Raft 算法,实现 Sentinel 选举。 Ratf 算法 我们前面说过,只要有了多个副本,就必然要面对副本一致性的问题。如果要所有的节点达成一致,必然要通过复制的方式实现。但是这么多节点,以哪个节点的数据为准呢? 所以必须选出一个 Leader。 所以数据保持一致需要两个步骤∶领导选举,数据复制。数据复制我们前面提过了。这里关注一下选举的实现。 Raft是一个共识算法(consensus algorithm)。比如比特币之类的加密货币,就需要共识算法。Spring Cloud 的注册中心解决方案 Consul也用到了Raft 协议。 Raft 的核心思想∶ 先到先得,少数服从多数。 先说一下,Sentinel的Raft实现跟原生的算法是有所区别的,但是大体思想一致。 raft算法演示 总结∶ Sentinle 的 Raft 算法和 Raft 论文略有不同。

  1. master 客观下线触发选举,而不是过了election timeout 时间开始选举。
  2. Leader 并不会把自己成为Leader的消息发给其他 Sentinel。其他 Sentinel等待Leader从slave选出master后,检测到新的master 正常工作后,就会去掉客观下线的标识,从而不需要进入故障转移流程。

下面才是真正的选举。 Redis 的 master 节点的选举规则又是什么样的呢? 对于所有的 slave 节点,一共有四个因素影响选举的结果,分别是断开连接时长、优先级排序、复制数量、进程 id

  1. 如果与哨兵连接断开的比较久,超过了某个阈值,就直接失去了选举权。
  2. 如果拥有选举权,那就看谁的优先级高,这个在配置文件里可以设置(replica-priority 100),数值越小优先级越高。
  3. 如果优先级相同,就看谁从 master 中复制的数据最多(复制偏移量最大),选最多的那个
  4. 如果复制数量也相同,就选择进程 id 最小的那个。

master节点确定之后,又怎么让其他的节点变成它的从节点呢?

  1. 选出 Sentinel Leader之后,由 SentinelLeader向某个节点发送slave of no one命令,让它成为独立节点。
  2. 然后向其他节点发送slave of xxxxxxx(本机IP端口),让它们成为这个节点的从节点,故障转移完成。

Sentinel功能总结

监控∶ Sentinel 会不断检查主服务器和从服务器是否正常运行。 通知∶如果某一个被监控的实例出现问题,Sentinel可以通过 API发出通知。 自动故障转移∶如果主服务器发生故障,Sentinel可以启动故障转移过程。把某台服务器升级为主服务器,并发出通知。 配置管理∶客户端连接到 Sentinel,获取当前的 Redis 主服务器的地址。

哨兵机制的不足

主从切换的过程中会丢失数据,因为只有一个 master。 只能单点写,没有解决水平扩容的问题。 如果数据量非常大,这个时候就要对 Redis 的数据进行分片了。 这个时候我们需要多个master-slave的 group,把数据分布到不同的group中。 问题来了,数据怎么分片?分片之后,怎么实现路由?

Redis分布式

一致性哈希

一致性哈希的原理∶ 把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织。因为是环形空间,0和 2^32-1 是重叠的。 假设我们有四台机器要哈希环来实现映射(分布数据),我们先根据机器的名称或者 IP 计算哈希值,然后分布到哈希环中(红色圆圈)。

现在有4条数据或者 4个访问请求,对key 计算后,得到哈希环中的位置(绿色圆圈)。沿哈希环顺时针找到的第一个 Node,就是数据存储的节点。

在这种情况下,新增了一个 Node5节点,只影响一部分数据的分布。

删除了一个节点Node4,只影响相邻的一个节点。

一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响。 但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)。 比如∶2个节点,5条数据,只有1条分布到Node2,4条分布到Node1,不均匀。

Node1设置了两个虚拟节点,Node2也设置了两个虚拟节点(虚线圆圈)。这时候有3条数据分布到 Node1,1 条数据分布到 Node2。

那这样一个一致性哈希算法,到底怎么实现呢? 哈希环是一个什么数据结构?虚拟节点又怎么实现? redis.clients.util.Sharded.initialize(),jedis 实例被放到了一颗红黑树TreeMap中。

当存取键值对时,计算键的哈希值,然后从红黑树上摘下比该值大的第一个节点上的 JedisShardInfo,随后从 resources 取出 Jedis。

获取红黑树子集,找出比它大的第一个节点。

代码语言:javascript复制
public S getShardInfo(byte[] key){
//获取比当前 key 的哈希值要大的红黑树的子集 
SortedMap<Long,S> tail = nodes.tailMap(algo.hash(key); 
if (tail isEmpty()){
// 没有比它大的了,直接从 nodes 中取出 
	return nodes.get(nodes.firstKey));
}
// 返回第一个比它大的 JedisShardInfo
return tail.get(tail.firstKey());
}
• 1
• 2
• 3
• 4
• 5
• 6
• 7
• 8
• 9
• 10
• 11

Redis Cluster

Redis Cluster 是在 Redis 3.0 的版本正式推出的,用来解决分布式的需求,同时也可以实现高可用。 数据分片有几个关键的问题需要解决∶

  1. 数据怎么相对均匀地分片
  2. 客户端怎么访问到相应的节点和数据
  3. 重新分片的过程,怎么保证正常服务

架构

Redis Cluster可以看成是由多个 Redis 实例组成的数据集合。客户端不需要关注数据的子集到底存储在哪个节点,只需要关注这个集合整体。 以3主3从为例,节点之间两两交互,共享数据分片、节点状态等信息。

数据分布

Redis 既没有用哈希取模,也没有用一致性哈希,而是用虚拟槽来实现的。 Redis创建了16384个槽(slot),每个节点负责一定区间的slot。比如 Node1负责0-5460,Node2负责5461-10922,Node3负责 10923-16383。

对象分布到Redis节点上时,对key用CRC16 算法计算再384,得到一个slot的值,数据落到负责这个 slot 的 Redis 节点上。 Redis 的每个master节点都会维护自己负责的slot。用一个 bit序列实现,比如∶序列的第0位是1,就代表第一个slot是它负责;序列的第1位是0,代表第二个slot不归它负责。 注意∶key 与slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系。 问题∶怎么让相关的数据落到同一个节点上? 比如有些 multikey操作是不能跨节点的,例如用户2673的基本信息和金融信息? 在 key 里面加入{hash tag}即可。Redis 在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。 问题∶新增或下线了Master 节点,数据怎么迁移(重新分配)?

数据迁移

因为key和slot的关系是永远不会变的,当新增了节点的时候,需要把原有的slot分配给新的节点负责,并且把相关的数据迁移过来。 问题∶ 只有主节点可以写,一个主节点挂了,从节点怎么变成主节点?

高可用和主从切换原理

当 slave 发现自己的 master 变为 FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下∶

  1. slave 发现自己的 master 变为 FAIL
  2. 将自己记录的集群currentEpoch加1(投自己一票),并广播FAILOVER_AUTH_REQUEST信息
  3. 其他节点收到该信息,只有 master 响应,判断请求者的合法性,并发送FAlLOVER_AUTH_ACK,对每一个epoch只发送一次 ack
  4. 尝试 failover 的 slave 收集 FAlILOVER_AUTH_ACK
  5. 超过半数后变成新 Master
  6. 广播 Pong 通知其他集群节点

总结∶ Redis Cluster 既能够实现主从的角色分配,又能够实现主从切换,相当于集成了 Replication 和 Sentinel 的功能。

Redis Cluster 特点

  1. 无中心架构。
  2. 数据按照slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
  3. 可扩展性,可线性扩展到1000个节点(官方推荐不超过1000个),节点可动态添加或删除。
  4. 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave做standby数据副本,能够实现故障自动failover,节点之间通过 gossip协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升。
  5. 降低运维成本,提高系统的扩展性和可用性。

0 人点赞