【Redis学习】3.高可用之主从复制


1 概述

复制,主要用于解决单点问题。生产上通常会备用多个节点,以备主节点宕机之患。同时,对于读场景多的情况,也可以做到负载均衡的效果。

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);

数据的复制是单向的,只能由主节点到从节点

主从复制的作用主要包括:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

2 原理

Redis 提供了主从复制模式,可以保证多台服务器的数据一致性,且主从服务器之间采用的是「读写分离」的方式。

  • 读操作:主节点、从节点都可以接收;
  • 写操作:首先到主节点执行,然后,主节点将写操作同步给从节点。
    主从复制示意图

2.1 过程

2.1.1 建立连接

主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情。

使用 replicaof(Redis 5.0 之前使用 slaveof)命令形成主节点和从节点的关系。

从节点开启主从复制,有3种方式

  • 配置文件:在从节点服务器的配置文件中加入:
    replicaof <masterip> <masterport>
  • 启动命令:redis-server启动命令后加入 --replicaof <masterip> <masterport>
  • 客户端命令:Redis服务器启动后,直接通过客户端执行命令:replicaof <masterip> <masterport>,则该Redis实例成为从节点。

ps: 通过命令replicaof no one,可以断开主从复制关系。

通过replicaof命令启动成功后,从节点会保存主节点的ipport,然后通过定时任务(间隔1s)向主节点发送建立连接请求,主节点通过后,从节点会发送ping命令验证连接的联通情况,通过之后,若从节点开启了身份验证,则还要经过这一步的验证,验证成功,从节点再向主节点发送端口号,以后主向从发送信息则通过这个端口进行;验证失败,断开连接,重试操作。

上述流程图如下所示:

建立连接过程

2.1.2 数据同步

主从复制连接建立起来后,主、从节点就可以开始进行数据同步了。

注意:在2.8版本之前只有全量复制,而2.8版本之后全量复制增量复制

  • 全量复制:比如第一次同步时.
  • 增量复制:只会把主从节点网络断连期间,主节点收到的命令,同步给从节点.

这里以2.8版本之后的Redis为例。从节点发送命令:

psync <runId> <offset>

其中,runId主节点的runIDoffset为从节点的复制进度。首次主从同步,runId?offset-1

每个 Redis 服务器在启动时都会自动产生一个随机的 ID来唯一标识自己。即:runId
offset为从节点目前从主节点同步的进度,首次同步时,由于没有,所以会给个默认值-1

主节点收到从节点的同步请求后,根据runIdoffset进行判定,该从节点是否曾经同步过。若同步过,则runId必等于自己的runId;否则,从节点为新的节点,需要全量复制进行同步。

runId验证通过,还要验证其offset,主节点自身维护的有一个叫做复制积压缓冲区的东东,用于存储写入的命令及其对应的偏移量,若offset存在于这个复制积压缓冲区中,则可进行增量复制,反之,则需进行全量复制

首次同步,主节点返回

FULLRESYNC <主节点的 runID> <主节点目前的复制进度 offset>

从节点收到响应后,会记录下这两个参数。这里有个地方需要注意,FULLRESYNC响应表示第一次复制采用的全量复制,也就是说,主节点会把当前所有的数据都复制给从节点。

接着,主节点执行bgsave,将RDB文件发送给从节点,从节点接收到 RDB 文件后,会先清空当前数据节点,然后加载 RDB 文件。

在主节点将数据同步给从节点的过程中,主节点不会被阻塞,仍然可以正常接收请求。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从节点的数据一致性,主节点会在内存中用专门的 replication buffer记录 RDB 文件生成后收到的所有写操作

三个时间间隙,主节点会将收到的写操作命令,写入到 replication buffer 缓冲区里:

  • 主节点生成 RDB 文件期间;
  • 主节点发送 RDB 文件给从节点期间;
  • 「从节点」加载 RDB 文件期间。

2.1.3 命令传播

从节点完成 RDB 文件的载入后,会回复一个确认消息给主节点。

接着,主节点将 replication buffer 缓冲区里所记录的写操作命令发送给从节点,从节点执行来自主节点 replication buffer 缓冲区里发来的命令,这时主从节点的数据就一致了。

至此,主从节点的第一次同步的工作就完成了。

主从节点在完成第一次同步后,双方之间就会维护一个 TCP 连接,用于命令传播。

命令传播

后续主节点可以通过这个连接继续将写操作命令传播给从节点,然后从节点执行该命令,使得与主节点的数据库状态相同。

而且这个连接是长连接的,目的是避免频繁的 TCP 连接和断开带来的性能开销

上面的这个过程被称为基于长连接的命令传播,通过这种方式来保证第一次同步后的主从节点的数据一致性

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制PINGREPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。

主从节点心跳机制
  • 主->从:默认每隔 10 秒发送 ping 命令给从节点,判断其存活性和连接状态,由repl-ping-slave-period参数控制,单位是
  • 从->主每隔 1 秒发送 replconf ack <offset>,上报自己的 offset,实时监测主从节点网络状态,检测命令丢失(没有断线情况下)。

上述全量复制同步的过程,总结如下图:

全量复制过程

如果为增量复制,其流程如下:

增量复制过程

能够完成增量复制的前提主要有:

  • 主节点复制积压缓冲区(repl_backlog_buffer):是一个「环形」缓冲区,默认大小是 1M,用于主从节点断连后,从中找到差异的数据;
  • 主从节点各自的复制偏移量(replication offset):标记上面那个缓冲区的同步进度,主从节点都有各自的偏移量,主节点使用 master_repl_offset 来记录自己「写」到的位置,从节点使用 slave_repl_offset来记录自己「读」到的位置。
  • 主节点(runId)

repl_backlog_buffer缓冲区是什么时候写入的呢?

在主节点进行命令传播时,不仅会将写命令发送给从节点,还会将写命令写入到 repl_backlog_buffer 缓冲区里,因此这个缓冲区里会保存着最近传播的写命令

网络断开后,当从节点重新连上主节点时,从节点会通过 psync 命令将自己的复制偏移量 slave_repl_offset发送给主节点,主节点根据自己的 master_repl_offsetslave_repl_offset 之间的差距,然后来决定对从节点执行哪种同步操作

  • 如果判断出从节点要读取的数据还在 repl_backlog_buffer 缓冲区里,那么主节点将采用增量复制的方式;
  • 如果判断出从节点要读取的数据已经不存在 repl_backlog_buffer 缓冲区里,那么主节点将采用全量复制的方式。

当主节点在 repl_backlog_buffer 中找到主从节点差异(增量)的数据后,就会将增量的数据写入到 replication buffer 缓冲区,这个缓冲区我们前面也提到过,它是缓存将要传播给从节点的命令。

增量复制过程

repl_backlog_buffer 缓行缓冲区的默认大小是 1M,并且由于它是一个环形缓冲区,所以当缓冲区写满后,主节点继续写入的话,就会覆盖之前的数据。因此,当主节点的写入速度远超于从节点的读取速度,缓冲区的数据一下就会被覆盖。

那么在网络恢复时,如果从节点想读的数据已经被覆盖了,主节点就会采用全量复制,这个方式比增量复制性能损耗要大很多

因此,为了避免在网络恢复时,主节点频繁地使用全量复制的方式,我们应该调整下 repl_backlog_buffer 缓冲区大小,尽可能的大一些,减少出现从节点要读取的数据被覆盖的概率,从而使得主节点采用增量复制的方式。
一般经验,repl_backlog_buffer大小采用如下方案设定:

repl-backlog-size = second * write_size_per_second

其中,

  • second:为从节点断线后重新连接上主节点所需的平均时间(以秒计算)。
  • write_size_per_second:则是主节点平均每秒产生的写命令数据量大小。

举个例子,如果主节点平均每秒产生 1 MB 的写命令,而从节点断线之后平均要 5 秒才能重新连接主节点。

那么 repl_backlog_buffer 大小就不能低于 5 MB,否则新写地命令就会覆盖旧数据了。

当然,为了应对一些突发的情况,可以将 repl_backlog_buffer 的大小设置为此基础上的 2 倍,也就是 10 MB

3 问题

3.1 Redis主从节点时长连接还是短连接?

长连接

3.2 怎么判断 Redis 某个节点是否正常工作?

Redis 判断节点是否正常工作,基本都是通过互相的 ping-pong 心态检测机制,如果有一半以上的节点去 ping 一个节点的时候没有 pong 回应,集群就会认为这个节点挂掉了,会断开与这个节点的连接。

Redis 主从节点发送的心态间隔是不一样的,而且作用也有一点区别:

  • Redis 主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态,可通过参数repl-ping-slave-period控制发送频率。
  • Redis 从节点每隔 1 秒发送 replconf ack{offset} 命令,给主节点上报自身当前的复制偏移量,目的是为了:
    • 实时监测主从节点网络状态;
    • 上报自身复制偏移量, 检查复制数据是否丢失, 如果从节点数据丢失, 再从主节点的复制缓冲区中拉取丢失数据。

3.3 主从复制架构中,过期key如何处理?

主节点处理了一个key或者通过淘汰算法淘汰了一个key,这个时间主节点模拟一条del命令发送给从节点,从节点收到该命令后,就进行删除key的操作

3.4 Redis 是同步复制还是异步复制?

Redis 主节点每次收到写命令之后,先写到内部的缓冲区,然后异步发送给从节点。

3.5 主从复制中两个 Buffer(replication buffer 、repl backlog buffer)有什么区别?

replication bufferrepl backlog buffer 区别如下:

  • 出现的阶段不一样:
    • repl backlog buffer 是在增量复制阶段出现,一个主节点只分配一个 repl backlog buffer
    • replication buffer 是在全量复制阶段增量复制阶段都会出现,主节点会给每个新连接的从节点,分配一个 replication buffer
  • 这两个 Buffer 都有大小限制的,当缓冲区满了之后,发生的事情不一样:
    • repl backlog buffer 满了,因为是环形结构,会直接覆盖起始位置数据;
    • replication buffer 满了,会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制。

3.6 如何应对主从数据不一致?

为什么会出现主从数据不一致?

主从数据不一致,就是指客户端从从节点中读取到的值和主节点中的最新值并不一致。

之所以会出现主从数据不一致的现象,是因为主从节点间的命令复制(命令传播)是异步进行的,所以无法实现强一致性保证(主从数据时时刻刻保持一致)。

具体来说,在主从节点命令传播阶段,主节点收到新的写命令后,会发送给从节点。但是,主节点并不会等到从节点实际执行完命令后,再把结果返回给客户端,而是主节点自己在本地执行完命令后,就会向客户端返回结果了。如果从节点还没有执行主节点同步过来的命令,主从节点间的数据就不一致了。

如何如何应对主从数据不一致?

  • 第一种方法,尽量保证主从节点间的网络连接状况良好,避免主从节点在不同的机房。
  • 第二种方法,可以开发一个外部程序来监控主从节点间的复制进度。具体做法:
    • Redis 的 INFO replication 命令可以查看主节点接收写命令的进度信息(master_repl_offset)和从节点复制写命令的进度信息(slave_repl_offset),所以,我们就可以开发一个监控程序,先用 INFO replication 命令查到主、从节点的进度,然后,我们用 master_repl_offset 减去 slave_repl_offset,这样就能得到从节点和主节点间的复制进度差值了。
    • 如果某个从节点的进度差值大于我们预设的阈值,我们可以让客户端不再和这个从节点连接进行数据读取,这样就可以减少读到不一致数据的情况。不过,为了避免出现客户端和所有从节点都不能连接的情况,我们需要把复制进度差值的阈值设置得一些。

4 参考


文章作者: Kezade
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kezade !
评论
  目录