看完这篇Redis主从复制,不会来找我

Redis高可用的方案包括持久化、主从复制(及读写分离)、哨兵和集群。其中持久化侧重解决的是Redis数据的单机备份问题(从内存到硬盘的备份);而主从复制则侧重解决数据的多机热备。此外,主从复制还可以实现负载均衡和故障恢复。

不过真正解决Redis的单点负载和内存问题,还是需要依赖集群模式

Redis的主从复制功能有两种模式,Redis2.8版本之前的旧版复制功能和新版的复制功能,新版复制是为了解决旧版复制功能的低效和复制的冗余,下面就介绍两种复制功能的流程与复制的工作原理。

Redis主从复制的流程

旧版复制的流程

看完这篇Redis主从复制,不会来找我
Redis主从复制

Redis的复制分为同步和命令传播两个操作。

  • 同步:将从服务器的数据库状态更新至主服务器所处的当前的数据库状态,即将主服务器的内存完全复制到从服务器
  • 命令传播:当主服务器接收客户端的命令并执行之后,主从服务器的状态就会不一致,而这个时候主服务器就会将命令传播到所有的从服务器,让主从服务器保持一致性
同步

在从服务器中执行SLAVEOF命令时,要求从服务器复制主服务器,同步主服务器的数据库状态。这个时候从服务器先会连接到主服务器,通过PING命令检查主从之间的网络连通性,然后执行将当前从服务器的数据库状态清空,并发送同步命令准备同步主服务器的数据。

  • 从服务器向主服务器发送SYNC命令
  • 主服务器收到SYNC命令之后执行BGSAVE命令,在后台生成RDB文件,并同时创建一个复制缓冲区记录从当前开始执行的所有写命令。
  • 当主服务器的BGSAVE执行完毕之后,会将生成的RDB文件通过网络发送到从服务器,从服务器接收这个RDB文件,并加载到内存中,当RDB文件加载完成之后,从服务器会向主服务器发送命令表示当前同步完成
  • 主服务器会将复制缓冲区中记录的所有写命令发送从服务器,从服务器这些命令,将从服务器的数据库状态更新到主服务器的当前状态,内存中的数据保持一致。
命令传播

在上述的初次的同步操作完成之后,主从服务器就会进入到命令传播阶段。每当主服务器执行写命令之后,就会将当前执行的写命令增量的同步到从服务器,保证主从服务器的状态保持一致,不过因为是异步复制和命令传播,所以主从服务器之间不能保持强一致性,而是最终一致性。

旧版复制的缺点

  • 第一次复制时,从服务器发送SYNC命令到主服务器,主服务器执行BGSAVE生成RDB快照文件,从服务器进行一次完整重同步来保证数据库的状态保持一致。
  • 当从服务器断线重连之后,也会发送一次SYNC命令,重复第一次复制的步骤,进行一次完整重同步,但是这样的效率是较慢的。

SYNC命令是一个非常消耗资源的操作:

  • 主服务器需要生成RDB文件,占用了主服务器的大量CPU、内存、磁盘IO等,在客户端请求较大时,会导致主服务器处理效率变慢
  • 主服务器在发送RDB文件到从服务器时,占用较大的网络资源
  • 从服务器在接收到RDB文件之后,需要更新当前数据库的状态,处于阻塞状态无法接受客户端请求。

新版主从复制流程

为了解决旧版主从复制中,断线重连时执行完整重同步比较耗费资源的问题,Redis从2.8版本开始,使用PSYNC命令代替了SYNC命令,提高了主从复制的效率。

PSYNC命令具有完整重同步和部分重同步两种模式:

  • 完整重同步:在主从的初次复制时,PSYNC命令表示完整重同步,其作用和SYNC命令作用相同,执行流程基本一致,都是通过生成完整的RDB文件发送到从服务器,然后发送复制缓冲区中的写命令来完成一次同步
  • 部分重同步:主要作用于断线重连的情况,从服务器断线后重新连接到主服务器时,如果满足部分重同步的条件,那么主服务器只会发送从服务器在断线这段时间内的增量写命令,而不是进行一次完整重同步,从服务器也只需要接收增量的部分命令,执行之后就可以完成数据库状态的更新。

部分重同步的实现

部分重同步的结构:
  • 主服务器的复制偏移量和从服务器的复制偏移量

    复制偏移量是为了记录当前主从复制之间的数据差距,判断主从是否处于一致状态,每次主服务器在同步一条命令给从服务器的时候,就会将主服务器的复制偏移量增加,从服务器在接收主服务器发送的命令后,也会将从服务器的复制偏移量增加。

  • 复制积压缓冲区

    复制积压缓冲区时为了记录从服务器断线时间内的写命令,使用一个队列来实现。主服务器每次在将写命令发送从服务器时,也会同步的将这个写命令记录到复制积压缓冲区,默认大小时1M。

  • 服务器的运行ID(run ID)

    在第一次进行主从复制的时候,主服务器会它的run Id发送给从服务器,从服务器会记录当前主服务器的运行ID是为了判断从服务器在重新连接时和第一次连接的服务器是否为同一个服务器

部分重同步的执行流程
看完这篇Redis主从复制,不会来找我
Redis部分重同步流程

在从服务器进行断线重连的过程中,也会判断当前是否满足部分重同步的条件,只有满足部分重同步的条件,才会进行部分重同步的过程,否则还是会执行完整重同步,因为需要保证数据的一致性。

  • 从服务器在断线重连之后,会向主服务器发送PSYNC命令和当前记录的主服务器的run ID、当前从服务在断线之前记录的复制偏移量offset
  • 主服务器接收到从服务器发送的PSYNC命令,并对比当前的服务器的id和从服务器发送过来的run ID是否一致,如果一致则可以进行部分重同步的操作,如果不同则不可以部分重同步,说明在从服务器断线期间,进行过主从切换的操作,所以需要从服务器进行一次完整重同步,重新记录当前新的主节点的信息
  • 如果run id相同,那么主服务器会根据接收到的offset复制积压缓冲区中查看从服务器的offset是否仍然在缓冲区中有记录,如果没有记录 ,那么就表示有命令丢失,需要对从服务器执行一次完整重同步,如果有记录,那么就可以找到从服务器断线期间的增量命令,那么就可以进行一次部分重同步
  • 如果数据在复制积压缓冲区中存在,那么主服务器向从服务器发送+CONTINUE命令,然后将复制积压缓冲区中的增量数据发送给从服务器,主从会重新回到一致的状态。

主从同步流程

完整重同步和部分重同步的大致流程:

看完这篇Redis主从复制,不会来找我
主从同步流程

Redis主从复制的原理

主从复制的实现

主从复制是将从服务器的数据完全同步为主服务器的数据,保证两个服务器的状态一致,并且主从复制是异步的复制过程,在同一时间,不能保证数据的强一致性。

主从复制的流程

  • 步骤一:设置主服务器的的地址和端口:

    SLAVEOF <master_id> <master_port>

  • 步骤二:建立套接字连接

    根据步骤一指定的ip和端口,向执行的服务器建立连接,并将从服务器作为主服务器的客户端

  • 步骤三:发送PING命令

    在步骤二的连接建立之后,第一件事就是发送PING命令,发送PING命令第一是为了检测建立的连接是否正确,主服务器是否可以收到从服务器发送的命令。第二是为了主从复制做准备,因为复制中主从服务器都是通过命令进行通信,先发送PING命令,检测主服务器是否可以正常接收并处理命令。主服务器在接收到PING命令之后向从服务器响应PONG命令,从服务器接收到主服务器发送的PONG命令,那么就表示连接状态正常,并且可以正常进行通信

  • 步骤四:身份验证

    如果步骤三中建立的连接正常,那么会根据时是否设置masterauth选项来判断是否进行身份认证,如果需要进行身份认证,那么从服务器会向主服务器发送一条AUTH命令,命令的参数为masterauth的值。

  • 步骤五:发送端口信息

    如果步骤四的身份认证成功,那么从服务器会执行命令replconf listening-port <port>,向主服务发送从服务器监听的端口号。

  • 步骤六:同步

    从服务器这时候会发送PSYNC命令,执行同步操作,将从服务器的数据库更新为主服务器的数据库的状态,保证数据的一致性。

    在同步阶段,主服务器会成为从服务器的客户端的角色,因为Redis中,只有客户端才能向Redis发送命令,并且被Redis执行。

  • 步骤七:命令传播

    当完成数据的初次同步之后,主服务器和从服务器的数据库的状态保持一致,主从服务器就会进入到命令传播阶段,在此阶段,主服务器只需要将执行的写命令通过网络链接发送给从服务器,从服务器接收并执行,就可以保证主从服务器的数据一致性(最终一致性)。

主从复制的心跳检测

主从服务器在进入到命令传播阶段之后,从服务器会每秒一次的频率向主服务器发送心跳连接,就是一条命令:REPLCONF ACK <replication_offset>

对主从服务器主要有三个作用:

  • 检测主从服务器的网络链接状态
  • 辅助实现min-slaves选项 -> 可以防止Redis的脑裂问题,在Redis-Sentinel文章中有讲哦
  • 检测命令的丢失

当写命令在网络中丢失之后,主服务器在从服务器发送的replconf ack命令可以感知到双方的数据不一致,那么会通过偏移量来进行补发命令,将主从服务器的数据恢复到一致性的状态。

Redis主服务主动补发命令是通过REPLCONF ACK <replication_offset>命令来判断,是Redis2.8之后特性,在Redis2.8之前,如果命令丢失,主从服务器是不会感知到的。

小结

  • 主从复制通过RDB快照文件和Redis自己实现的COW机制对修改的数据页进行复制的机制以及复制缓冲区来实现
  • 主从复制在Redis2.8之后分为完整重同步和部分重同步两种操作。
  • 部分重同步通过服务器运行ID、复制积压缓冲区、复制偏移量offset实现
  • 主服务器通过命令传播的方式实现主从的状态保持一致性,不同复制是 异步的实现过程,从服务器和主服务器之间通过心跳检测来判断数据库内存状态的一致性。

相关问题

  • 为什么主从全量同步一直失败?

    在主从全量同步时,你可能会遇到同步失败的问题,具体场景如下:

    slave 向 master 发起全量同步请求,master 生成 RDB 后发给 slave,slave 加载 RDB。

    由于 RDB 数据太大,slave 加载耗时也会变得很长。

    此时你会发现,slave 加载 RDB 还未完成,master 和 slave 的连接却断开了,数据同步也失败了。

    之后你又会发现,slave 又发起了全量同步,master 又生成 RDB 发送给 slave。

    同样地,slave 在加载 RDB 时,master / slave 同步又失败了,以此往复。

    这是怎么回事?

    其实,这就是 Redis 的「复制风暴」问题。

    什么是复制风暴?

    就像刚才描述的:主从全量同步失败,又重新开始同步,之后又同步失败,以此往复,恶性循环,持续浪费机器资源。

    为什么会导致这种问题呢?

    如果你的 Redis 有以下特点,就有可能发生这种问题:

    主从在全量同步数据时,master 接收到的写请求,会先写到主从「复制缓冲区」中,这个缓冲区的「上限」是配置决定的。

    当 slave 加载 RDB 太慢时,就会导致 slave 无法及时读取「复制缓冲区」的数据,这就引发了复制缓冲区「溢出」。

    为了避免内存持续增长,此时的 master 会「强制」断开 slave 的连接,这时全量同步就会失败。

    之后,同步失败的 slave 又会「重新」发起全量同步,进而又陷入上面描述的问题中,以此往复,恶性循环,这就是所谓的「复制风暴」。

    如何解决这个问题呢?我给你以下几点建议:

    如果你也踩到了这个坑,可以通过这个方案来解决。

    • master 的实例数据过大,slave 在加载 RDB 时耗时太长
    • 复制缓冲区(slave client-output-buffer-limit)配置过小
    • master 写请求量很大
    1. Redis 实例不要太大,避免过大的 RDB
    2. 复制缓冲区配置的尽量大一些,给 slave 加载 RDB 留足时间,降低全量同步失败的概率


原文始发于微信公众号(指尖上的代码):看完这篇Redis主从复制,不会来找我

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/39936.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!