zoukankan      html  css  js  c++  java
  • redis主从复制的核心原理

    此内容参考蒋德钧老师将讲义

    Redis具有高可靠性,具有两层含义:一是数据尽量少丢失,二是服务尽量少中断。AOF和RDB保证了前者,而对于后者,redis的做法是增加副本冗余,将一份数据同时保存在多份实例上。即使有一个实例出现了宕机,需要一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。

    多实例保存同一份数据,听起来好像不错,但是,必须要考虑一个问题:这么多副本,他们之间的数据一致性如何保持?数据读写操作可以发给所有实例吗?

    实际上redis提供了主从库模式,以保证数据副本的一致性,主从库之间采用的是读写分离的方式。

    • 读操作:主库、从库都可以接受
    • 写操作:首先到主库执行,然后,主库将写操作同步给从库。

    主从库如何进行第一次同步

    当我们启动多个redis实例的时候,他们相互之间就可以通过slaveof 命令形成主库和从库关系,之后会按照三个阶段完成数据的第一次同步。

    例如,现有实例1(ip:172.16.19.3)和实例2(ip:172.16.19.5),在实例2上执行以下命令,实例2就变成了实例1的从库,并从实例1上复制数据

    slaveof 172.16.19.3 6379
    

    接下来,就要学习主从库间数据第一次同步的三个阶段了。

    第一阶段是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立连接,并告诉主库即将建立连接,主库确认回复后,主从库间就可以开始同步了。

    具体来说,从库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包含了主库的runID和复制进度offset两个参数

    • runID:每个redis实例启动时都会自动生成一个随机的runID,用来唯一标记这个实例。当从库和主库第一个复制时,因为不知道主库的runID,所以设置为?。
    • offset,此时为-1,表示第一次复制。
      主库收到psync命令后,会用fullresync 响应命令带上两个参数:主库runID和主库目前的复制进度offset,返回给从库。从库收到响应后,会记录下这两个参数。
      这里有个地方需要注意,fullresync响应表示第一次复制采用的时全量复制,也就是说,主库会把当前所有的数据都复制给从库

    在第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照RDB。

    具体来说,主库执行bgsave命令,生成RDB文件,接着将文件发给从库。从库接收到RDB文件后,会先情况当前数据库,然后加载RDB文件。这是因为从库在执行slaveof命令之前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。

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

    最后,也就是第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成RDB文件发送后,就会把此时replocation buffer中修改操作发送给从库,从库再执行这些操作。这样一来,主从库就实现同步了。

    之后的同步不出现意外,都是采用增量复制完成

    主从级联模式分担全量复制时主库压力

    通过分析主从库间第一次数据同步的过程,可以看见,一次增量复制中,对于主库来说,需要完成两个耗时的操作:生成RDB文件和传输RDB文件。

    如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于fork子进程生成RDB文件,进行数据全量同步。fork这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输RDB文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。那么,有没有好的解决方案可以分担主库压力呢?

    其实是有的,这就是“主-从-从”模式。

    在刚才介绍的主从库模式中,所有的从库都是和主库连接,所有的全量复制也都是和主库进行的。现在,可以通过主-从-从 模式将主库生成RDB和传输RDB文件的压力,以级联的方式分散到从库上

    简单来说,就是在部署主从集群的时候,可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,可以再选择一些从库(例如三分之一的从库),在这个从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。

    slaveof 所选从库ip 6379
    

    这样一来,这些从库就会知道,在进行同步时,不用再和主库进行交互了,只要和级联的从库进行写操作同步就行了,这样可以减轻主库的压力。如下图所示:

    主从库间网络断了怎么办(增量复制)

    在redis2.8之前,如果主从库在命令传播时出现了网络闪断,那么,主库就会和从库重新进行一次全量复制,开销非常大。

    在redis2.8之后,网络断了之后,主从库会采用增量复制的方式继续同步。

    那么,增量复制时,主从库间具体是怎么保持同步的呢?这里的奥妙就在于repl_backlog_buffer这个缓存区。我们先来看下他是如何用于增量命令的同步的。

    当主从库断连后,主库会把断联期间收到的写操作命令,写入replication buffer,同时也会把这些操作写入repl_backlog_buffer 这个缓存区。

    repl_backlog_buffer是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己读到的位置

    刚开始的时候,主库和从库的写读位置是在一起的,这算是他们的起始位置。随着主库不断接收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置,我们通常用偏移量来衡量这个偏移距离的大小,对主库来说,对应的偏移量就是master_repl_offset。主库收到新的写操作越多,这个值就会越大。

    同样,从库在复制完写操作命令后,他在缓冲区中的读位置也开始逐步便宜刚才的起始位置,此时,从库已复制的偏移量slave_repl_offset也在不断增加。正常情况下,这两个偏移量基本相等。

    主从库的连接恢复后,从库首先会给主库发送psync,并把runID和自己当前的offset(slave_repl_offset)发送给主库,主库会判断自己的offset(master_repl_offset)和slave_repl_offset之间的差距。

    • 注意 如果这里发送过去的runID 不是此时的主库ID,这就说明可能原来的主库下线,哨兵重新选举了主库,那么此时就需要进行全量复制
    • 增量复制不止是网络断联才用,在进行完第一次全量复制之后,主从之间就是进行增量复制。当从节点发送的runID与主节点一致时,说明之前进行过同步,可以进行增量复制

    在网络断联阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset会大于slave_repl_offset。此时,主库只用把master_repl_offset和slave_repl_offset之间的命令操作同步给从库就行。

    就像上面示意图中间部分,主库和从库之间相差了put d e 和 put d f两个操作,在增量复制时,主库只需要把他们同步给从库就行了。

    不过需要注意的是repl_backlog_buffer是一个环形缓冲区,所以在缓冲区写满后,主库还会继续写入,此时,就会覆盖掉之前的写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这就会导致主从数据不一致
    此时可以增大缓冲区解决问题。如果还是会出现出从offset超过缓冲区大小,那么就不能进行增量复制(这样会丢失数据),转而使用全量复制。

  • 相关阅读:
    【AHOI2009】 维护序列
    Codeforces Hello 2019 F. Alex and a TV Show[bitset+莫比乌斯反演]
    Codeforces Hello 2019 D. Makoto and a Blackboard[DP+数论+概率期望]
    Codeforces Hello 2019 ABCDF题解
    [BZOJ1042][HAOI2008]硬币购物[容斥原理+背包]
    Codeforces Round #529 (Div. 3)题解
    [BZOJ4311]向量[线段树分治+计算几何+二分/三分]
    [BZOJ1076][SCOI2008]奖励关[状压DP+概率期望]
    [POJ3368][UVA11235] Frequent values[ST表]
    [USACO5.5]矩形周长Picture[扫描线+线段树]
  • 原文地址:https://www.cnblogs.com/liuzhidao/p/14812723.html
Copyright © 2011-2022 走看看