redis · 11 4 月, 2021 0

redis 主从复制实现原理

主从库间数据第一次同步

当我们启动多个Redis实例时, 实例之间可以通过replicaof(5.0版本之前采用slaveof)命令形成主库和从库的关系。

主从复制拓扑图如下:

数据同步步骤

  • psync命令: 该命令由从节点向主节点发送建立连接的请求, 主库会根据从库的参数启动复制. 该命令包含了两个参数:

    • 主库的runId: runId表示了每个实例启动时随机生成的随机ID, 用来标识redis实例。当从库和主库第一次复制数据时, 因为不知道主库的runID, 因此将 runId设置为”?”

    • 复制进度offset: 默认值为 -1, 表示第一次复制.

  • FULLRESYNC: 由主库向从库响应, 表示第一次采用全量复制。

  • 主库采用BGSAVE命令生成全量RDB文件, 并将文件发送给从库。

  • 从库接收到RDB文件后,会清空当前数据库,然后加载从主库同步过来的RDB文件

  • 主库在同步数据到从库期间,能够正常执行其他请求,并将写请求写入到replication buffer

  • 主库将replication buffer中的命令,发送给从库

主从模式复制时的主库压力

在复制数据的过程中,有两个耗时操作:

  1. 生成 RDB文件:

    • 虽然采用BGSAVE命令异步生成文件,但在fork主进程时,会导致主进程的阻塞。同时会将当前内存数据产生副本,占用更多内存信息。

    • 传输RDB文件会占用主库网络带宽,造成请求响应变慢, 无法处理更多客户端请求等问题

主-从-从模式

为了缓解主库在较多从库链接导致主库性能下降问题,可以采用主-从-从模式,基本原理就是减少主库同步数据的压力,转而将部分的同步请求转移到从库执行。

在数据同步完成之后,主从之间依然维持者链接,用于主库将写操作同步到从库,这个过程被称作基于长连接的命令传播,可以避免频繁建立连接的开销。

主从库断线重连机制

  • 在2.8版本之前,主从库断线重连会采用全量同步的方式,重新保持数据一致性。

  • 在2.8之后, 则采用增量赋值的方式继续同步。

    • 在以上步骤中,主库为了解决在赋值rdb文件的过程中,增量命令的丢失,创建了replication buffer ,用于缓存写命令。但是在从库断开链接后, 主库会将写命令写入到repl_backlog_buffer缓冲区中(此时replication buffer已经不存在)。

    • 从库恢复之后,通过psync命令将当前offset发送到主库,告知主库从slave_repl_offset开始发送写命令。

什么是repl_backlog_buffer

repl_backlog_buffer是一个环形缓冲区, 主库记录最新写命令位置, 从库会记录当前已读位置.

repl_backlog_buffer中,有两个关键偏移量:

  • master_repl_offset: 用于记录当前主库命令偏移量,随着主库接收到的写请求越大,该值越大

  • slave_repl_offset: 从库在复制完成写命令后,它在缓冲区中的读位置也在逐渐增大。

在正常情况下,这两个偏移量基本相等。

缓冲区大小设置

因为repl_backlog_buffer是一个环形缓冲区, 所以缓冲区写满后, 主库会继续写入。如果从库读取数据缓慢,会导致主库的命令覆盖从库还未读取的命令。导致从库数据不一致性的情况。

repl_backlog_size

通过该参数, 可以调整repl_backlog_size缓存区的大小,该值的确认可以根据服务器资源充足情况设置较大,或者根据一下公式做评估:

 缓冲区空间大小 = 主库写入命令速度 * 命令操作大小 - 主从库间网络传输命令速度 * 操作大小

在考虑业务高峰或者业务攻击等特殊情况,我们可以将缓冲区扩大1倍,保证写入命令不会覆盖从库未同步命令。

repl_backlog_buffer 与 replication buffer 区别

  • repl_backlog_buffer:为了解决从库断开重连后,能够快速找到主从差异而设计的环形缓冲区,从而避免全量同步带来的性能开销。如果环形缓冲区从库未同步命令被主库覆盖,那么从库重新连接后,必须进行一次重新全量同步。因此建议将该缓冲区设置大一些,防止全量同步的概率。

  • Replication Buffer: 该缓存是Redis为客户端分配的写出缓冲区,所有数据的交互都是通过该缓冲区进行。

    • Redis把数据写入到当前缓存中,然后再把buffer中的数据发送到client socket中完成数据交互

    • 在主从同步过程中,从库也是作为Redis客户端存在的,通过该缓冲区将命令发送到从库。

    • 如果从库处理数据非常慢,会导致当前缓冲区持续增长,消耗大量内存资源。甚至OOM的错误,所以在Redis中,可以通过client-ouotput-buffer-limit参数限制这个buffer的大小,如果超过限制,主库会强制断开这个client链接。

    当从库再次链接上来时,会再次发起复制清库,那么此时可能导致恶性循环,引发赋值风暴。