Redis面试系列:聊一聊Redis的持久化和主从复制(四)

前言

大家都可能会被问:Redis与Memcache的区别是什么?最容易记住的可能就是:Redis支持丰富的数据类型和Redis的持久化。
Redis服务发生宕机后,在重启时还能进行数据恢复,这也是现在都使用Redis的原因吧。

一、Redis持久化机制是什么?

RDB持久化(默认)

在Redis配置文件redis.conf默认配置了如下信息:

#指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
#这里表示900秒内有1个更改操作,300秒内有10个更改以及60秒内有10000个更改
#如果想禁用RDB持久化的策略,只有不设置任何save指令,或者save传入一个空字符串也可以
save 900 1
save 300 10
save 60 10000

RDB能关闭吗? 配置【save “”】 就能关掉。
但是在主从复制模式中是关不掉的RDB模式。(主机是把RDB数据发送给从机的)

除了上面的配置会触发持久化数据之外,使用命令也可以触发RDB持久化:bgsave和save。

执行bgsave后Redis会fork一个子进程 ,然后就把数据先写到一个临时文件 temp-xxxxx.rdb, 再用这个临时文件替换上次持久化好的文件 : dump.rdb 。
save命令会阻塞主进程,直到保存完成为止,一般不会用。

RDB的优点

  1. RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,非常适合做冷备
  2. 相对于AOF,基于RDB数据文件来重启和恢复Redis进程会更加快速

RDB就是数据文件,直接加载到内存即可
AOF存放的是指令日志,需要回放所有指令命令

RDB的缺点:丢失数据可能会比较多

另外值得一提的是,因为AOF文件的更新频率通常比RDB文件的更新频率高
所以:如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。·
只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。

AOF

开启AOF(append only file)后,会把每次操作命令保存到appendonly.aof文件中。

redis.conf配置:
#是否启用aof持久化方式。
appendonly no


# The name of the append only file (default: "appendonly.aof")
#指定日志aof文件名,默认是appendonly.aof
appendfilename "appendonly.aof"

redis> SET msg “hello”
对于上述命令,服务器将产生包含以下内容的AOF文件
*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
*3\r\n$3\r\nSET\r\n$3\r\nmsg\r\n$5\r\nhello\r\n

触发机制:

#默认
appendfsync everysec

# appendfsync always
# appendfsync no

appendfsync选项的值分别是 always、everysec 和 no:

  • always:每一次写操作都会调用一次fsync,这时数据是最安全的,但是性能会受到影响。当发生故障停机时,AOF持久化也只会丢失一个事件循环中所产生的命令数据。
  • everysec:每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。当发生故障停机时,只会丢失一秒钟的命令数据。
  • no:Redis不会主动调用fsync去将AOF日志内容同步到磁盘,完全依赖于操作系统的fsync调用。

这几种模式跟MySQL的 redo log的写入机制有点类似: Redo log

文件的写入和同步
为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。
这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。
为此,系统提供了fsync和fdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

AOF重写机制

作用是压缩文件大小

当AOF文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。
当AOF文件大小的增长率大于配置项时自动开始写。

auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100

重写干了些什么事情:首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是AOF重写功能的实现原理。

AOF优点

  1. 可以更好的保证数据不丢失
  2. AOF日志文件以append-only模式写入,写入性能非常高
  3. 文件过大会重写,对其进行重写压缩
  4. 特别适合做灾难性误删除的紧急恢复(执行了flushall,只有没有进行rewrite,就可以拷贝AOF文件,把最后一条flushall命令删掉,再回放该文件)

AOF缺点: 文件会比较大、恢复数据时间会比较久。

AOF文件的载入与数据还原详细步骤

  1. 创建一个不带网络连接的伪客户端(fake client):因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样。
  2. 从AOF文件中分析并读取出一条写命令。
  3. 使用伪客户端执行被读出的写命令。
  4. 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止。

二、主从复制原理

旧版复制功能同步步骤

从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成,以下是SYNC命令的执行步骤

1、从服务器向主服务器发送SYNC命令

2、收到SYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令

3、当主服务器的BGSAVE命令执行完毕时,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态

4、主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态。

命令传播

在同步操作执行完毕之后,主从服务器两者的数据库将达到一致状态,但这种一致并不是一成不变的,每当主服务器执行客户端发送的写命令时,主服务器的数据库就有可能会被修改,并导致主从服务器状态不再一致。

为了让主从服务器再次回到一致状态,主服务器需要对从服务器执行命令传播操作:主服务器会将自己执行的写命令,也即是造成主从服务器不一致的那条写命令,发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态。

当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里面。

SYNC命令的缺陷是一个非常耗费资源的操作:
每次执行SYNC命令,主从服务器需要执行以下动作:

  1. 主服务器需要执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘I/O资源。
  2. 主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的时间产生影响。
  3. 接收到RDB文件的从服务器需要载入主服务器发来的RDB文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求。

新版复制功能的实现

为了解决旧版复制功能在处理断线重复制情况时的低效问题,Redis从2.8版本开始,使用PSYNC命令代替SYNC命令来执行复制时的同步操作。

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

  • 其中完整重同步用于处理初次复制情况:完整重同步的执行步骤和SYNC命令的执行步骤基本一样,它们都是通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步。
  • 部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态。

部分重同步的实现

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

复制偏移量
主服务器和从服务器会分别维护一个复制偏移量:

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

通过对比主从服务器的复制偏移量,程序可以很容易地知道主从服务器是否处于一致状态

复制积压缓冲区

复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小为1MB。

当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:

  • 如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分重同步操作。
  • 相反,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作。

服务器运行ID

每个Redis服务器,不论主服务器还是从服务,都会有自己的运行ID;运行ID在服务器启动时自动生成,由40个随机的十六进制字符组成。

当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器,而从服务器则会将这个运行ID保存起来。

当从服务器断线并重新连上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID:

  • 如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以继续尝试执行部分重同步操作。
  • 相反地,如果从服务器保存的运行ID和当前连接的主服务器的运行ID并不相同,那么说明从服务器断线之前复制的主服务器并不是当前连接的这个主服务器,主服务器将对从服务器执行完整重同步操作。

PSYNC命令的实现

PSYNC命令的调用方法有两种:

  • 如果从服务器以前没有复制过任何主服务器,或者之前执行过SLAVEOF no one命令,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1命令,主动请求主服务器进行完整重同步(因为这时不可能执行部分重同步)。
  • 相反地,如果从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC 命令:其中runid是上一次复制的主服务器的运行ID,而offset则是从服务器当前的复制偏移量,接收到这个命令的主服务器会通过这两个参数来判断应该对从服务器执行哪种同步操作。

根据情况,接收到PSYNC命令的主服务器会向从服务器返回以下三种回复的其中一种:

  • 如果主服务器返回:FULLRESYNC <runid> <offset> 回复,那么表示主服务器将与从服务器执行完整重同步操作:其中runid是这个主服务器的运行ID,从服务器会将这个ID保存起来,在下一次发送PSYNC命令时使用;而offset则是主服务器当前的复制偏移量,从服务器会将这个值作为自己的初始化偏移量。
  • 如果主服务器返回+CONTINUE回复,那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了。
  • 如果主服务器返回-ERR回复,那么表示主服务器的版本低于Redis 2.8,它识别不了PSYNC命令,从服务器将向主服务器发送SYNC命令,并与主服务器执行完整同步操作。

在这里插入图片描述

心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:REPLCONF ACK <replication_offset>

replication_offset是从服务器当前的复制偏移量。

发送REPLCONF ACK命令对于主从服务器有三个作用:

  • 检测主从服务器的网络连接状态。
  • 辅助实现min-slaves选项。
  • 检测命令丢失。

总结

  • Redis 2.8以前的复制功能不能高效地处理断线后重复制情况,但Redis 2.8新添加的部分重同步功能可以解决这个问题。
  • 部分重同步通过复制偏移量、复制积压缓冲区、服务器运行ID三个部分来实现。
  • 在复制操作刚开始的时候,从服务器会成为主服务器的客户端,并通过向主服务器发送命令请求来执行复制步骤,而在复制操作的后期,主从服务器会互相成为对方的客户端。
  • 主服务器通过向从服务器传播命令来更新从服务器的状态,保持主从服务器一致,而从服务器则通过向主服务器发送命令来进行心跳检测,以及命令丢失检测。

参考《Redis设计与实现》

猜你喜欢

转载自blog.csdn.net/Oooo_mumuxi/article/details/105958846