0%

Redis分布式-主从复制详解

在实际生产环境中,为了避免单点故障,Redis肯定不会以单机的形式部署,而是会同时部署多个实例。Redis多机数据库的实现主要有三种方式,分别是主从复制、Sentinel和集群,本文主要会详细阐述主从复制的实现原理

本文主要内容参考自《Redis设计与实现》

如何使用主从复制

Redis中,可以通过SLAVEOF命令或者设置slaveof选项,可以让一个服务器去复制另一个服务器。被复制的服务器称为主服务器(master),进行复制的服务器称为从服务器。

旧版复制功能实现

在2.8版本之前,Redis的复制主要分为同步(sync)命令传播(command propagate)两个操作:

  1. 同步:将从服务器的数据库状态更新成主服务器的数据库状态。
  2. 命令传播:将作用于主服务器的写命令,传播给从服务器进行执行,从而保证主从数据库状态一致。

同步

当从服务器刚执行SLAVEOF命令时,首先做的就是同步操作,将从服务器的数据库状态更新成主服务器的数据库状态。

从服务器通过向主服务器发送SYNC命令来完成同步操作,SYNC命令的执行步骤如下:

  1. 从服务器向主服务器发送SYNC命令。
  2. 主服务器收到SYNC命令之后,开始执行BGSAVE命令生成RDB文件。在生成RDB文件期间,将写命令记录在一个缓冲区中。
  3. 主服务器发送RDB文件文件给从服务器,从服务器载入该RDB文件。
  4. 主服务器发送生成RDB文件期间的写命令给从服务器,从服务器重放这些命令。此时从服务器状态和主服务器状态一致,同步操作完成。

sync

命令传播

在同步操作完成之后,后续主服务器执行的写命令会以命令传播的方式发送给从服务器,从而保证主从数据库状态一致。

旧版复制功能的缺陷

当初次复制时,旧版复制功能没有任何问题。但是当因为网络问题,主从服务器短暂掉线重连之后,此时依然会触发复制。虽然这种方式可以让主从数据库状态重新一致,但是性能和效率都会很低。因为重连之后的复制,仍然要执行同步操作,生成完整的RDB文件,从服务器依然要载入该RDB文件。实际上,在掉线期间,只是丢失了少量写命令,此时执行完整的复制操作,显然效率太低了。

新版复制功能实现

为了解决旧版复制功能在掉线重复制的低效问题,在2.8版本之后,Redis使用PSYNC命令代替原来的SYNC命令来执行同步操作。

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(patial resynchronization)两种模式

  • 完整重同步:用于处理初次复制的情况。跟旧版的SYNC命令基本一致,都是让主服务器生成并发送RDB文件和在此期间的写命令给从服务器来完成同步操作的。
  • 部分重同步:用于处理掉线后的重复制情况。当从服务器掉线后重新连接主服务器,如果条件允许,可以让主服务器只将掉线期间执行的写命令发送给从服务器,从而达到主从数据库状态重新一致。

psync

部分重同步实现

部分重同步功能主要由以下三个部分构成:

  • 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量
  • 主服务器的复制积压缓冲区(replication backlog)
  • 服务器运行ID

复制偏移量

执行复制的双方-主服务器和从服务器会分别维护一个复制偏移量:

  • 主服务器每次向从服务器传播N个字节的数据时,就会将自己的复制偏移量加N。
  • 从服务器每次收到主服务器传播来的N个字节数据时,也会将自己的复制偏移量加N。

如果主从数据库状态一致,那么它们的复制偏移量一定是相等的,反之,则表示数据库状态不一致。因此当发生掉线重连时,主从服务器的复制偏移量一定不相等。

复制积压缓存区

复制积压缓存区是由主服务器维护的固定长度的先进先出(FIFO)队列,默认大小为1MB。当主服务器进行命令传播时,不仅会将写命令发送给所有的从服务器,还会将写命令写入复制积压缓冲区中。因此,复制积压缓冲区会保存最近传播的写命令,并且复制积压缓冲区为每个字节记录了相应的复制偏移量。

当从服务器重连之后,从服务器会通过PSYNC命令将自己的复制偏移量发送给主服务器,主服务器会根据这个偏移量来决定采用哪种同步操作。

  • 如果offset偏移量之后的数据仍然存在复制积压缓冲区中,那么采用部分重同步。
  • 如果offset偏移量之后的数据不在复制积压缓冲区中,那么采用完整重同步。

服务器运行ID

每个Redis服务器,不论是主服务器还是从服务器,都会有自己的运行ID。运行ID在服务器启动的时候生成,由40位随机字符组成。当从服务器进行初次复制时,会将对应的主服务器运行ID也保存下来。通过这个运行ID,就能确定重连上的主服务器是不是掉线之前的主服务器,如果是的话,则尝试之前的部分重同步,反之,则执行完整重同步。

PSYNC命令的实现

PSYNC命令调用方式有两种:

  1. 如果从服务器之前没有复制过任何主服务器,那么从服务器在开始一次新的复制时,会发送PSYNC ? -1命令,主动请求完整重同步。
  2. 如果从服务器已经复制过主服务器,那么开始一次新的复制时,会发送PSYNC <runid> <offset>命令,主服务根据runidoffset参数决定使用何时复制方式。

接收到PSYNC命令的主服务器,可能会返回以下三种结果中的一种给从服务器:

  1. +FULLRESYNC <runid> <offset>:执行完整重同步。其中runid表示主服务器的运行ID,从服务器会将其保存下来;offset是主服务器的复制偏移量,从服务器会将其作为自己的初始偏移量。
  2. +CONTINUE:执行部分重同步。主服务器会将缺少的那部分数据发送给从服务器。
  3. -ERR:复制错误。表示主服务器版本低于2.8,不能识别PSYNC命令。

psyncshixian

主从复制的不足

主从复制解决了数据备份和性能(通过读写分离)的问题,但是还是存在一些不足:

  1. RDB 文件过大的情况下,同步非常耗时。
  2. 在一主一从或者一主多从的情况下,如果主服务器挂了,对外提供的服务就不可用了,单点问题没有得到解决。如果每次都是手动把之前的从服务器切换成主服务器,这个比较费时费力,还会造成一定时间的服务不可用。

原创不易,觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~

欢迎关注我的开源项目:一款适用于SpringBoot的轻量级HTTP调用框架