详解Redis主从模式及哨兵和集群

主从复制

单点问题:

在分布式系统中,如果某个服务器程序,只有一个节点(也就是一个物理服务器)来部署这个服务器程序的话,那么可能会出现以下问题:

  1. 可用性问题:如果这个机器挂了,那么意味着怎么服务器也就挂了

  2. 性能/支持的并发量也是有限的

在分布式系统里,人们常常期望借助多台服务器来部署 Redis 服务,进而构建一个 Redis 集群。构建好的 Redis 集群能够为整个分布式系统提供更为高效、稳定的数据存储服务。

在分布式系统中,有多个服务器部署Redis,往往有以下几种方式:

  1. 主从模式

  2. 主从+哨兵

  3. 集群


配置主从模式:

在若干个Redis节点中,有“主”节点,也有“从”节点

例如:

在 Redis 的主从架构中,从节点需严格遵循主节点的指令,其内部数据会始终与主节点保持同步,可将从节点视作主节点的数据副本。

最初,仅由单个主节点存储大量数据。引入从节点后,主节点的数据会被复制到各个从节点中。此后,主节点对数据的任何修改操作,都会同步更新到从节点上。

主从模式的核心优势在于显著提升“读操作”的并发处理能力和可用性。

不过,“写操作”只能由唯一的主节点执行,也就是说,主节点在整个架构中仅有一个。

在实际的业务场景里,“读操作”的发生频率通常远高于“写操作”。

若某个从节点出现故障,对系统的影响相对较小。因为此时仍可从其他正常运行的从节点读取数据,基本不会影响业务的正常开展。

但要是主节点出现故障,情况就较为棘手了。系统将只能进行数据读取操作,而无法执行写入操作,这无疑会对业务的正常运行造成一定程度的影响。

鉴于当前仅有一台云服务器可用,我们需要在该服务器上启动多个 Redis 服务器进程,并进行相应的配置,构建一个包含一个主节点和两个从节点的架构。

其中,主节点的默认端口设定为 6379,两个从节点的默认端口分别为 6380 和 6381。

1.给从节点设置配置文件

root@iZbp1j57qexhmdoyijkse9Z:/etc/redis# mkdir redis-conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis# cd redis-conf/
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# pwd
/etc/redis/redis-conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# cp /etc/redis/redis.conf ./slave1.conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# cp /etc/redis/redis.conf ./slave2.conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# ls
slave1.conf  slave2.conf

在它们的配置文件中修改:

修改端口号

修改其 daemonize 为 yes。


删除它们的进程 重新启动

此时用命令info replication 可以查看它们当前的属性

127.0.0.1:6379> set key 111
OK
127.0.0.1:6379> get key
"111"
127.0.0.1:6379>
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# redis-cli -p 6380
127.0.0.1:6380> get key
"111"
127.0.0.1:6380> set key 222
(error) READONLY You can't write against a read only replica.

此时主节点插入了数据 从节点也可以查询到,但是从节点不能插入数据

此时我们的结构已经搭建完成

拓扑结构:

若干个节点之间,按照怎么样的方式来进行组织连接:一主一从、一主多从、树状主从结构。

一主一从

这是一种极为基础且简洁的架构,其主要作用在于当主节点遭遇宕机故障时,从节点能够及时提供故障转移支持,保障系统的基本运行。

然而,若系统中写入数据的请求量过大,主节点将承受相当大的压力,这可能会影响其性能和稳定性。

针对这一问题,我们可以采取一个优化策略:关闭主节点的 AOF(Append - Only File)持久化机制,仅在从节点上保留 AOF 持久化功能。这样做既能在一定程度上确保数据的安全性,又能避免因持久化操作对主节点性能造成的负面影响。

不过,这种策略也存在一定的风险。一旦主节点出现故障,由于其没有 AOF 文件,就可能会导致部分数据丢失。而且,在后续进行主从同步时,从节点的数据也可能会被清除,从而造成更大范围的数据损失。

为了弥补这一缺陷,我们可以采用如下改进方法:当主节点发生故障后,让主节点从从节点获取 AOF 文件中的数据。通过这种方式,在主节点恢复后,能够最大程度地还原数据,减少数据丢失带来的影响,提升系统的数据可靠性和稳定性。

一主多从

在数据量庞大的应用场景中,为有效减轻系统压力,可采用负载均衡策略,将读命令合理分配到不同的从节点上执行。这样一来,各个从节点能够共同承担读操作的负载,避免单个节点因负担过重而影响性能。

与此同时,对于那些执行时间较长、较为耗时的读命令,可以指定由一台专门的从节点来处理。如此操作,能够防止这类耗时操作对整个系统的稳定性造成破坏,确保系统始终保持高效、稳定的运行状态。

然而,随着从节点数量的不断增加,新的问题也随之而来。为保证各个从节点的数据与主节点保持一致,每一条数据都需要在主节点与多个从节点之间进行多次传输。这种频繁的数据同步操作会显著增加主节点的负载,可能导致主节点的性能下降,甚至影响整个系统的正常运行。

树形结构:

树据写入节点 A 之后会同步给 B 和 C 节点,B 节点进一步把数据同步给 D 和 E 节点。当主节点需要挂载等多个从节点时为了避免对主节点的性能干扰,但同步的延时是比刚才长的

原理:

主从节点建立复制流程图:

  1. 先保存主节点的ip和端口变量等信息

  2. 建立TCP的网络连接,三次握手,为了验证通信双方是否能够正确的读写数据

  3. 验证主节点是否能够正常的工作 从节点发送ping命令 主节点返回pong

  4. 如果主节点设置了 requirepass 参数,则需要密码验证

数据同步:

Redis提供了psync命名完成主从数据的同步

PSYNC 的语法格式 PSYNC replicationid offset

理解下面过程,先了解一下前提概念:

  1. replicationid/replid (复制id)

    • 主节点的复制id,主节点重新启动,或者从从节点晋升到主节点的时候,也会生成这个id(即便是同一个主节点,在它重启的时候,生成的id都是不同的)

    • 主节点和从节点建立复制关系的时候,就会从主节点这边获取replicationId

  2. offset(偏移量)

在 Redis 的主从架构中,主节点和从节点都会维护一个关键参数——偏移量,这是一个整数值。

主节点在运行过程中会接收到大量用于修改数据的命令,每一条命令都会占用一定数量的字节空间。主节点会持续对这些修改命令所占用的字节数进行累加,得到的累加值便是主节点的偏移量。

从节点的偏移量则清晰地反映了当前从节点的数据同步进度,它表明从节点已经将主节点的数据同步到了哪一个具体的位置。

当从节点和主节点的偏移量数值一致时,这就意味着从节点已经成功完成了与主节点的数据同步,二者的数据状态保持一致。

replid + offset 共同标识了一个 “数据集”.

如果两个节点, 他们的 replid 和 offset 都相同, 则这两个节点上持有的数据, 就一定相同.

在 info replication 命令中可以查看到:

psync过程:

1)从节点发送 psync 命令给主节点,replid 和 offset 的默认值分别是 ? 和 -1.

2)主节点根据 psync 参数和自身数据情况决定响应结果:

  • 如果回复 +FULLRESYNC replid offset,则从节点需要进行全量复制流程。
  • 如果回复 +CONTINEU,从节点进行部分复制流程。
  • 如果回复 -ERR,说明 Redis 主节点版本过低,不支持 psync 命令。从节点可以使用 sync 命令进行全量复制

同步过程分为:全量复制和部分复制。

  • 全量复制: 通常应用于初次复制的场景。在 Redis 发展的早期阶段,其所支持的复制功能仅有全量复制这一种方式。这种复制方式会将主节点上的全部数据一次性地传输给从节点。当主节点的数据量较为庞大时,全量复制会给主节点、从节点以及网络带来巨大的开销。主节点需要消耗大量资源来准备和发送数据,从节点则要花费较多时间和资源接收并加载这些数据,同时,大量的数据传输也会对网络带宽造成极大的压力。
  • 部分复制: 在主从复制过程中,网络闪断等情况时有发生,这往往会导致数据丢失。为应对此类场景,当从节点与主节点的连接恢复后,若条件适宜,主节点会向从节点补发丢失的数据。由于补发的数据量相较于全量数据而言要小得多,因此能够显著降低数据复制过程中的开销,有效规避全量复制所带来的高成本问题。

主要就是看offset这里的进度~

如果offset写作-1,就是获取全量数据;

如果写作具体的整数,那么就是从当前偏移量位置获取数据;

全量复制:

当首次主节点进行数据同步的时候或者主节点不方便部分复制的时候;

全量复制流程:

  1. 从节点发送 psync 命令给主节点进行数据同步,由于是第一次进行复制,从节点没有主节点的运行 ID 和复制偏移量,所以发送 psync ? -1。

  2. 主节点根据命令,解析出要进行全量复制,回复 +FULLRESYNC 响应。

  3. 从节点接收主节点的运行信息进行保存。

  4. 主节点执行 bgsave 进行 RDB 文件的持久化。

  5. 主节点发送 RDB 文件给从节点,从节点保存 RDB 数据到本地硬盘。

  6. 主节点将从生成 RDB 到接收完成期间执行的写命令,写入缓冲区中,等从节点保存完 RDB ⽂件后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写入到收到的 rdb 文件中. 保持主从一致性。

  7. 从节点清空自身原有旧数据。

  8. 从节点加载 RDB 文件得到与主节点一致的数据。

  9. 如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进行 bgrewrite 操作,得到最近的 AOF 文件。

部分复制:

当从节点需要从主节点进行全量复制时,会产生高昂的开销。实际上,在某些情况下,从节点自身已经拥有主节点的大部分数据,此时便无需进行全量复制。

此外,若出现网络波动,主节点对数据所做的修改将无法及时同步到从节点。待网络恢复正常,主节点与从节点重新建立连接后,就需要对这期间产生差异的数据进行同步。

流程:

  1. 当主从节点之间出现网络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并终端复制连接。

  2. 主从连接中断期间主节点依然响应命令,但这些复制命令都因网络中断无法及时发送给从节点,所以暂时将这些命令滞留在复制积压缓冲区中。

  3. 当主从节点网络恢复后,从节点再次连上主节点。

  4. 从节点将之前保存的 replicationId 和 复制偏移量作为 psync 的参数发送给主节点,请求进行部分复制。

  5. 主节点接到 psync 请求后,进行必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据,并响应 +CONTINUE 给从节点。

  6. 主节点将需要从节点同步的数据发送给从节点,最终完成一致性。


实时复制:

在主从节点成功建立连接后,主节点会借助 TCP 长连接,将后续接收到的所有数据修改操作源源不断地传输给从节点。从节点会依据这些接收到的请求,同步修改自身的数据,从而确保主从节点之间的数据始终保持一致。

这种 TCP 长连接会采用心跳包机制来维持连接状态。

心跳包机制
  • 主节点方面:默认情况下,主节点每隔 10 秒会向从节点发送一个 ping 命令,从节点在接收到该命令后,会立即返回 pong 进行响应。
  • 从节点方面:从节点默认每隔 1 秒会向主节点发送一个特定请求,在这个请求中,从节点会上报当前自身复制数据的进度,该进度以偏移量(offset)来表示。

若主节点检测到与从节点之间的通信延迟超过了 repl-timeout 配置项所设定的值(默认值为 60 秒),则会判定从节点已下线,并断开与该从节点的复制客户端连接。不过,当从节点恢复连接后,心跳机制会自动继续运行,以保障主从节点间的通信和数据同步能够持续正常进行。


哨兵(Sentinel)

在 Redis 的主从模式下,若主节点发生故障宕机,系统就只剩下从节点继续运行。此时,从节点虽能继续提供数据读取服务,但无法执行写入操作。并且,从节点无法自动升级为主节点,承担起原本主节点的职责。若依靠人工干预来解决这一问题,显然不太现实,毕竟不可能安排人员全天候盯着服务器。

为应对这一困境,Redis 引入了“哨兵”(Sentinel)机制,其目的在于确保系统能够保持“高可用”状态。当主节点出现故障时,Redis Sentinel 能够自动检测到故障的发生,并迅速完成故障转移操作。同时,它还会及时通知应用程序,告知主节点已更换。通过这种方式,真正实现了系统的高可用性,最大限度地减少了因主节点故障对业务造成的影响。

哨兵架构:

每一个哨兵节点都是单独的进程,并且会提供奇数个

Redis哨兵的核心功能就是

这些哨兵会对现有的主从节点建立长连接,定期发送心跳包,借助上述的机制,就可以及时发现某个主机挂了;

如果从节点挂了,那么其实没有关系;

如果主节点挂了,那么哨兵就要发挥它们的作用;

此时一个哨兵发现挂了还不够,需要多个哨兵节点来共同认同这件事,为了防止误判的情况;如果发现主节点确实挂了,那么哨兵们就会在这些从节点中推荐一个新的主节点;挑选出新的主节点之后,哨兵节点就会控制这个新的主节点,执行让其他的从节点转移到新的主节点上面;最后哨兵会通知客户端程序,告知新的主节点是哪个,让后续客户端再进行写操作针对新的主节点进行操作~~~

通过上面的介绍,可以看出 Redis Sentinel 具有以下几个功能:

  • 监控: Sentinel 节点会定期检测 Redis 数据节点、其余哨兵节点是否可达。
  • 故障转移: 实现从节点晋升(promotion)为主节点并维护后续正确的主从关系。
  • 通知: Sentinel 节点会将故障转移的结果通知给应用方。

部署哨兵(基于docker)

由于只有一个云服务器,但是需要6个节点,基于在虚拟机上实现;

docker可以认为是一个“轻量级”的虚拟机;起到了虚拟机这样隔离环境的效果,但是又不会吃很多硬件资源;

略………………….

哨兵选举原理:

1.主观下线:

当主节点宕机时,此时主节点和三个哨兵节点的心跳包没有了;

站在哨兵节点的角度上,主节点发生了严重故障,因此三个哨兵节点会把主节点判断为主观下线(SDown);

2.客观下线:

多个哨兵节点认为主节点挂了,那就是挂了,当故障得票数 >= 配置的法定票数

这就是客观下线(ODown)

3.选举出哨兵的leader

此时接下来只需要从剩下的从节点中选一个当主节点出来,但这个工作不需要多个哨兵都参与,只需要选出一个代表(leader),由这个leader负责升级从节点到主节点这个过程;

哨兵之间的选举涉及到Raft算法:

Raft 的领导选举是通过“投票”机制来完成的,每个节点都会投票选举一个领导者。在选举过程中,候选人会向其他节点请求投票,如果得到大多数节点的支持,它就成为新的领导者。

简而言之, Raft 算法的核心就是 “先下手为强”. 谁率先发出了拉票请求, 谁就有更大的概率成为 leader.

无论选到谁作为leader都不重要,只需要能选出来一个节点即可~

4) leader 挑选出合适的从节点成为新的主节点

挑选规则:

  1. 比较优先级. 优先级高(数值小的)的上位. 优先级是配置文件中的配置项( slave-priority 或者 replica-priority ).
  2. 比较 replication offset 谁复制的数据多, 高的上位.
  3. 比较 run id , 谁的 id 小 谁上位.

当某个 slave 节点被指定为 master 之后,

  1. leader 指定该节点执行 slave no one , 成为 master

  2. leader 指定剩余的 slave 节点, 都依附于这个新 master


集群

上述提到的哨兵模式,确实在一定程度上提升了系统的可用性。然而,实际存储数据的主体依旧是主节点和从节点,所有的数据均存于这些主从节点之中。一旦数据量超出主从节点的内存容量,便极有可能引发严重问题。

为解决这一内存不足的难题,我们可以引入多台机器,部署多组主从节点,进而构建一个规模更大、功能更强大的整体,这便是所谓的集群。

举例来说,若有 1TB 的数据,可将其分割为三份,如此一来,每组机器仅需存储整个数据全集的 13 即可,大大减轻了单组主从节点的存储压力。

数据分片算法

数据可以分层多份,那么要怎么样分,才是最合适的呢?

1.哈希求余

我们借鉴哈希表的核心思想,利用哈希(hash)函数将一个键(key)映射为一个整数。接着,将这个整数对数组的长度取余,便能得到一个数组下标。

假设存在 N 个分片,我们采用从 0 到 N - 1 的序号对这些分片进行编号。

当 N 取值为 3 时,会有 [100, 120] 这 21 个哈希值的分布情况(这里假定通过计算得出的哈希值为简单整数,这样便于我们直观观察)。

优点: 简单高效, 数据分配均匀.

但是缺点更加明显,一旦需要扩容,原有的映射规则就会被破坏,此时需要重新计算,重新排列,所有需要大量的搬运

如上图可以看到, 整个扩容一共 21 个 key, 只有 3 个 key 没有经过搬运, 其他的 key 都是搬运过的.

2.一致性哈希算法

在传统的哈希求余算法里,确定每个键(key)所属的分片时,分片结果往往呈现出交替出现的特征。这种交替特性使得数据在不同分片间的分布较为分散,当需要对数据进行迁移或重新分配时,会极大地增加搬运成本。

与之形成鲜明对比的是,一致性哈希算法对这一问题进行了有效改进。在一致性哈希算法的机制下,原本交替出现的分片分配情况得以优化,键所对应的分片能够连续出现。如此一来,数据的分布更加集中和有序,显著降低了数据搬运过程中的成本和复杂性,提升了系统的整体性能和效率。

此时,如果新增一块分片,那么只需在环上重新安排一块区间

此时只需将0号分片的数据搬运到3号分片即可,那么1号和2号分片的区间是不变的~

但是由于1号和2号数据不变,那么它们所属的分片上的数据量就不均匀了,有的多有的少,数据倾斜;

3.哈希槽分区算法 (Redis 使用)

为了解决上述搬运成本高和数据分配不均匀的问题,Redis引入了哈希槽算法;

hash_slot = crc16(key) % 16384 crc16 也是一种 hash 算法.

16384 其实是 16 * 1024, 也就是 2^14.

相当于是把整个哈希值, 映射到 16384 个槽位上, 也就是 [0, 16383].

然后再把这些槽位比较均匀的分配给每个分片. 每个分片的节点都需要记录自己持有哪些分片.

假设当前有三个分片, 一种可能的分配方式:

• 0 号分片: [0, 5461], 共 5462 个槽位

• 1 号分片: [5462, 10923], 共 5462 个槽位

• 2 号分片: [10924, 16383], 共 5460 个槽位

如果需要进行扩容, 比如新增一个 3 号分片, 就可以针对原有的槽位进行重新分配.

比如可以把之前每个分片持有的槽位, 各拿出一点, 分给新分篇.

⼀种可能的分配方式:

• 0 号分片: [0, 4095], 共 4096 个槽位

• 1 号分片: [5462, 9557], 共 4096 个槽位

• 2 号分篇: [10924, 15019], 共 4096 个槽位

• 3 号分片[4096, 5461] + [9558, 10923] + [15019, 16383], 共 4096 个槽位

搭建集群:

通常集群的搭建都是三个主节点,六个从节点这样的形式

搭建过程复杂略……….


主节点宕机处理流程:

  • 自动故障检测

节点间通过 Gossip 协议 定期交换状态信息。若某主节点被多数节点标记为不可达(通过心跳超时判断),集群会触发故障转移。

  • 故障转移流程

    1. 从节点发起竞选。

    2. 其他主节点投票选出新的主节点。

    3. 原主节点的槽重新分配给新主节点。

    4. 客户端自动重定向到新主节点。


小结:

主从模式主要是数据冗余和读写分离。主节点处理写,从节点复制数据并处理读,这样可以提高读的吞吐量。但主从模式的问题在于主节点单点故障,这时候就需要哨兵来监控和自动故障转移。哨兵确保当主节点宕机时,能自动选一个从节点升级为主,保证系统的高可用性。不过,主从加哨兵还是单主节点,写操作还是受限于主节点的性能,无法水平扩展。

接下来是集群,它通过分片解决数据量过大和写性能的问题。集群将数据分散到多个主节点,每个节点负责一部分数据,这样写操作可以分布到不同节点,提高了整体性能。同时,每个主节点有对应的从节点,保证高可用。用户可能需要处理大量数据或高并发写入,这时候集群就比主从加哨兵更合适。

对比总结

架构 核心目标 优点 缺点 适用场景
主从模式 数据冗余、读写分离 简单易用,成本低 手动故障恢复,单点写入瓶颈 小规模应用,数据备份
哨兵模式 高可用性(自动故障转移) 自动化故障转移,服务发现 无法扩展写性能,不分片 中等规模,高可用需求
集群模式 数据分片 + 高可用 + 水平扩展 支持大规模数据和高并发,高可用 配置复杂,事务受限 大数据量、高并发、高可用需求
  • 主从模式主从 + 哨兵集群模式

随着业务增长,逐步从单点冗余过渡到自动化高可用,最终通过分片解决性能和容量瓶颈。

  • 集群模式 是终极方案,但复杂度高,需根据实际需求权衡是否必要。