vlambda博客
学习文章列表

Redis主从模式下是如何实现数据同步的?

写在前面


最近在做基于老项目的迭代工作,领导的意思是代码随便改,只要功能实现即可。我看了项服务依赖,其中redis这块是一主两从,三哨兵的配置。我很快搭建完毕,各个功能也都正常,但对于其中的来龙去脉一知半解,为了系统的搞懂它,便趁着这个机会把redis熟悉下。

概述


先大致总结下,redis在master-slave模式下的数据同步,可分为初始化同步命令传播同步。初始化同步,是在slave启动时需要从master拉取数据时的同步。slave会通过slaveof配置向master发送同步指令,master在接收指令后会执行RDB持久化操作,完成后会将RDB文件发送给slave,加载RDB文件,此刻master和slave数据保持一致。命令传播同步是在初始化同步后,对master操作的指令,会传播至slave,master充当了客户端的角色,使得数据始终保持一致;

如上所述,我对同步操作做了简单的总结,迫切得知结果的同学看上面即可。对于redis主从同步还有比较多的细节、设计思路、优化值得我们去学习,下面我就针对各个点进行讲解。

数据同步


发送SYNC同步命令是在slave启动时,master接收到同步命令后,会调用BGSAVE,执行持久化,但是RDB的持久化不会存储命令执行后的写操作,master会将此部分的写操作记录到缓冲区中。持久化完成后首先会发送RDB文件供slave加载,加载完毕后,紧接着会将缓冲区中的写操作供slave执行。如下图

Redis主从模式下是如何实现数据同步的?

至此slave初始化完成并在此时与master数据保持一致,但如果客户端又向master发送了写操作,一致的状态不就又被破坏了么?

在初始化完成后的主从模式下,master如果接收到客户端发送的写操作,首先会调用自身的写命令,然后会将命令传播至各salve节点,使master-slave数据始终保持一致。

版本缺陷


初始化同步其实可以分为两种场景,一种是首次进行初始化,一种是断线重连初始化;大家都知道redis在做持久化操作时是比较耗费CPU和内存资源的,对于首次初始化的slave来说可以一定程度的容忍,但是生产环境瞬息万变,谁也无法保证磁盘读写可以一直高效、网络可以一直稳定,如果频繁的断线重连,对于master来说就要频繁的执行BGSAVE,必定会影响redis正常的业务吞吐。

新版同步


在2.8版本之前的redis会有断线重新初始化同步操作的缺陷,影响性能,在2.8之后对SYNC做了升级改造。升级后的指令为PSYNC,它会根据条件选择执行完整同步还是部分同步。完整同步工作流程和低版本一致;部分同步在条件成立的情况下,会将断线后的master的写操作发送给slave去执行。断线重连后的同步流程如下图

Redis主从模式下是如何实现数据同步的?

由此可以看出升级后的PSYNC在条件符合的情况下,可以解决断线后同步效率问题,slave重连后也不会因为加载RDB而线程阻塞,master也不会每次断线都耗费资源生成RDB文件。

同步实现


部分同步是有前提条件的,这里就会涉及到复制偏移量复制积压缓冲区运行ID

  • 复制偏移量

master和slave在同步时,双方都会分别维护一个复制偏移量。master在向slave传播写命令时,会记录每个写命令的字节和对应的复制偏移量。假设slave-master重连后,slave会检查自身的复制偏移量和master维护的是否相等,如果不相等,则说明slave-master尝试重连的时间段内,客户端向master执行了写操作,由于网络问题slave没有接收到,无法更新复制偏移量,所以不相等。


  • 复制积压缓冲区

复制积压缓冲区是master单独维护的,缓冲区默认大小是1M。用来记录具体的复制偏移量和对应的复制偏移字节,如图

命令传播时如果复制双方的复制偏移量不相等,并在短时间内恢复了连接,slave会将自己的复制偏移量发送给master,master就会到复制积压缓冲区中查找断线后的字节,并传播给slave,salve和master数据重新保持了一致。


  • 运行ID

运行ID是redis服务器运行的唯一标识,也是执行部分同步的条件。slave-master在第一次同步时master会将自己的RUNID传给slave,slave会将它保存起来,在断线恢复连接时,会根据保存的RUNID和新建立连接的master的RUNID作比较,只有相等时才会执行部分同步,相反如果不等,就证明在断连前和断连后的master不是同一个,便会执行完整同步。

综上,新版的同步流程如下

复制的实现


  • 设置ip和端口

slave需要配置master的执行ip和端口号。


  • 套接字建立连接


  • PING的作用

建立套接字连接后,还未进行数据传输,此时slave会向master执行PING命令,因为后续的复制是一个比较耗时的操作,所以要验证网络的稳定性。这里的PING不仅可以验证网络是否超时,还可以验证master的状态,比如master正在进行大数据的处理,就会向slave返回错误,表示当前无法分配资源处理本次复制请求。


  • 执行复制

在密码验证完毕后,slave会发送PSYNC命令(流程不再赘述)。


  • 执行命令传播

当完成复制操作后,后续就进入命令传播阶段,master接收客户端写命令,传播至slave,slave执行。

心跳检测


心跳检测主要用于命令传播阶段,用来检测salve和master的网络状态。复制的双方通过发送和接收REPLCONF ACK命令进行检查,超过1秒钟master没有接收到salve发送的心跳检测命令,就被判定主从的网络出现问题(默认1秒)。

写在后面


本文是对《Redis的设计与实现》一书的理解,然后进行归纳总结。