Redis的持久化机制

Sat, May 15, 2021 阅读时间 2 分钟

Redis的持久化

Redis 的读写都是在内存中,所以它的性能较高,但在内存中的数据会随着服务器的重启而丢失,为了保证数据不丢失,我们需要将内存中的数据存储到磁盘,以便 Redis 重启时能够从磁盘中恢复原有的数据,而整个过程就叫做 Redis 持久化。

Redis的可持久化也是redis和memcached的主要区别之一,Memcached没有持久化机制。

Redis持久化的方式

快照方式RDB

RDB(Redis DataBase),某一个时刻,将当前内存中存储的所有数据形成一个快照,并以二进制形式写入磁盘。

文件追加方式AOF

AOF(append only file),将操作命令,以文本追加的形式写入文件。

混合持久化方式

Redis4.0以后支持的方式,由于AOF的文件占用空间大,而RDB的文件占用空间小,但是RDB丢数据比较多,所以有了这种AOF和RDB混合的持久化优化方式,触发时会将当前的内存快照转为RDB文件,但是后续记录的仍是AOF的逐条写入。这样既可以让持久化文件变得小一些,可以提高重启时数据恢复的速度。同时AOF机制也能降低RDB机制的数据丢失风险。

RDB

将某一时刻的内存快照,以二进制的形式写入磁盘,生成dump.rdb文件。

触发方式

手动触发

手动触发有两种方式:savebgsave命令

  1. save命令:

    在客户端执行save就会触发redis的持久化,这种方式会阻塞redis的主线程。

    127.0.0.1:6379> save
    OK
    
  2. bgsave命令:

    是save的后台版本,fork()一个子进程执行save,可以立即返回,不会阻塞主线程。

    127.0.0.1:6379> bgsave
    Background saving started
    

自动触发

有三种情况可以进行自动触发:

  1. 配置参数save m n

    设置如果在m秒内,如果有n个键发生改变,就执行bgsave命令触发RDB持久化;这个命令可以设置多个,多个之间是或的关系,即任意一个条件满足都会触发持久化。

  2. 执行命令flushall

    flushall命令用于清空redis数据库,执行这条命令时,如果redis配置开启了持久化,就会在清空前,执行一次持久化操作。

  3. 主从同步时触发:

    当从节点执行全量复制操作时,主节点会执行一次bgsave命令,生成rbd文件,并将rdb文件传给从节点。

自动触发的配置参数可以写在redi的配置文件中:

# RDB 保存的条件
save 900 1
save 300 10
save 60 10000

# bgsave 失败之后,是否停止持久化数据到磁盘,yes 表示停止持久化,no 表示忽略错误继续写文件。
stop-writes-on-bgsave-error yes

# RDB 文件压缩
rdbcompression yes

# 写入文件和读取文件时是否开启 RDB 文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。
rdbchecksum yes

# RDB 文件名
dbfilename dump.rdb

# RDB 文件目录
dir ./
  • save参数:配置自动触发RDB持久化的条件
  • rdbcompression:默认是yes,表示开启RDB文件压缩,redis会采用LZF算法进行压缩
  • rdbchecksum:默认是yes,表示写入文件和读取文件时是否开启RDB文件检查,如果有损坏,在redis启动时会终止启动

可以通过在客户端执行config get [参数名]命令来查看redis的某个参数值。

配置的设置方式:

  1. 手动修改redis的配置文件redis.conf,永久有效,但是需要重启redis
  2. 使用config set [参数名] “参数值” 的方式进行运行时修改,但是这种方式重启redis后会丢失。比如要禁用redis持久化,可以用命令 config set save ""

数据恢复

当redis服务器启动时,如果redis的根目录下有dump.rdb文件,则会自动加载该文件并恢复数据。可以通过redis的启动日志查看是否有加载数据。

RDB的优缺点

优点

  • 二进制的数据存储,占用磁盘空间小,更适合做备份文件,对容灾恢复很有用,并且加载数据的速度也很快,redis重启时速度快。
  • RDB的自动持久化机制会fork一个子进程,不会阻塞主进程

缺点

  • RDB是根据配置的策略进行持久化的,redis挂掉会丢失上次持久化到现在的数据量
  • RDB需要fork子进程进行持久化,且会加载内存中的所有数据,如果数据集很大,fork的操作可能很耗资源,虽然持久化的过程是不阻塞主线程的,但是创建子进程的时间可能会偏长,甚至高达几毫秒到一秒。

RDB的COW机制

copy-on-write

COW(copy-on-write)写时复制机制,是一种当多个线程需要操作同一份数据时,减少内存分配的机制。

在Redis主线程fork一个子进程执行bgsave或者bgrewriteaof命令时,子进程会和父进程共享一个内存空间,如果父进程此时没有写操作,那么子进程就可以顺利读取数据写入磁盘,只有在父进程发生写操作修改内存数据时,才会真正去分配内存空间并复制数据,而且也只是复制被修改的内存页中的数据,并不是全部的内存数据;

COW机制的主要好处是

  • 减少分配和复制资源时带来的瞬时延迟
  • 减少不必要的内存分配

可以看出,COW机制在写少读多时很有效,但是如果写操作变多,那复制的性能就会变差。

所以如果是dict出现rehash时,那么写入操作将是不可避免的,在执行bgsave或者bgrewriteaof创建子进程生成RDB文件时,为了提高COW的效率,dict的负载因子阈值就会提升到5,以减少写入操作。

COW机制在Go的原子操作中也有很多应用。

AOF

AOF(Append Only File)中文是附加到文件,顾名思义 AOF 可以把 Redis 每个键值对操作都记录到文件(appendonly.aof)中。

AOF的配置

查看是否开启AOF

通过命令config get appendonly可以查看到redis是否开启了AOF:

127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"

开启AOF持久化

Redis 默认是关闭 AOF 持久化的,想要开启 AOF 持久化,有以下两种方式,命令行或者配置文件:

  1. 修改配置文件,进行以下设置,完成后要重启redis:

    appendonly yes
    
  2. 运行时在命令行修改配置,只在一次启动过程中生效:

    config set appendonly yes
    

AOF触发持久化的方式

AOF持久化也包括自动触发和手动触发两种

自动触发

两种条件可以触发AOF,满足AOF设置的触发策略,或者满足AOF重写时,都会触发AOF

  1. 满足AOF设置的触发策略

    AOF的触发策略通过参数appendfsync设置:

    127.0.0.1:6379> config get appendfsync
    1) "appendfsync"
    2) "everysec"
    

    这个参数有三个可选值:

    • always:每条redis的写入操作都会写入磁盘,如果redis宕机,最多丢失一条数据,这种性能较差,很少人用,因为每次写都要操作磁盘,就失去了redis作为内存数据库的优势。
    • everysec:每隔一秒写入一次磁盘,最多会丢失一秒的数据。
    • no:不设置写入的规则,根据当前操作系统决定何时写入磁盘,linux的话默认是30s写入一次。

    最长使用的就是默认值everysec。

  2. 满足AOF重写时触发

    AOF重写:由于AOF是不停的追加写入语句到dump.aof文件的,所以这个文件会越来越大,为了缩小这个文件的体积,可以对文件中的一些写入语句进行合并,已达到缩小文件体积的目的。

    重写的实现:触发AOF重写需要满足两个参数

    • auto-aof-rewrite-min-size:允许AOF重写的最小dump.aof文件大小,默认是64MB。
    • auto-aof-rewrite-percentage:AOF重写的大小比例,默认是100%,即只有当前的AOF文件,比最后一次的AOF文件大一倍时才触发重写(加入测试时每秒同步一次,就意味着这一秒里插入了大量的数据)。

    AOF重写的流程:先生成一个新文件,然后根据旧的aof文件写入新文件,合并其中一些操作,然后交换两个文件,删掉旧的文件即可。

    在触发重写时会先立刻进行一次AOF持久化

手动触发

命令行执行bgrewriteaof即可手动立刻触发一次aof写入。

127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started

数据恢复

dump.aof文件同样是保存在redis服务的根目录,也同样是随着redis启动时自动加载,并恢复数据。

持久化文件的加载规则:

  • 如果只开启了AOF,redis启动时只会加载aof文件。
  • 如果只开启了RDB,则只会加载rdb文件。
  • 如果同时启动了AOF和RDB,则会只加载aof文件。

持久化文件截断的问题

在AOF写入时如果redis挂掉,那么最后一条记录会被截断,那么重启redis时这条数据就无法恢复,并且会导致redis无法启动,可以设置参数aof-load-truncated=yes,来让redis启动时忽略最后一行的错误数据,而正常启动。

如果是AOF文件中间的部分数据出现截断的现象,上面的参数就没有用了,可以使用两种方式解决:

  1. 使用aof修复工具,命令行输入redis-check-aof,会跳转到aof文件的错误的那一行,然后可以手动修复。
  2. 如果无法手动修复,可以使用redis-check-aof --fix命令,自动修复aof文件,这样会导致错误数据之后的所有数据都被丢弃。

AOF的优缺点

优点

  • 相比rdb,aof的数据更完整,一般都采用1s刷一次磁盘的策略,最多只会丢1s的数据
  • 追加的方式写入,如果出现异常,也可以通过上面两种方式修复。
  • aof文件易于人类理解,如果使用了flushall命令清空了数据库,那么只需要将aof文件中最后一行的flushall命令删除,然后重启redis即可恢复,rdb文件因为是一个时间的内存快照则是没用。

缺点

  • aof文件比rdb文件大得多。
  • 在redis负载较高时,RDB效率更高,因为AOF会阻塞主线程
  • RDB是拷贝内存快照,而AOF是追加写入磁盘,理论上说RDB更不容易出现错误。

混合持久化方式

Redis4.0后增加了混合持久化的方式,结合rdb和aof的优点,redis5.0以后这个是默认开启的。

配置混合持久化

同样有两种方式可以开启混合持久化:

  • 命令行方式:

    config set aof-use-rdb-preamble yes
    
  • 配置文件方式

    aof-use-rdb-preamble yes
    

混合持久化的实现方式

在进行AOF重写时,会将当前的内存快照以RDB的形式写入aof文件,而后续则继续以aof的格式一条条书写。这样文件大小就大大见减小了,同时rdb的数据恢复也会更快。

这样aof文件就会是这样:

数据恢复

同样的,redis会加载相同目录下的dump.aof文件,然后前面rdb的部分就用rbd的方式恢复,后面aof的部分就用aof的方式恢复。

混合持久化方式的优缺点

优点

结合了rdb和aof的优势,让redis的数据恢复更快,同时减小了文件大小,并且还能尽量减少数据丢失,和资源消耗。

缺点

  • aof文件的可读性消失了
  • 不能向前兼容,只有redis4.0以后的版本能用

关于Redis持久化的一些技巧

  • 持久化能保证数据不丢,但同时也影响了Redis的性能,所以需要一个平衡。
  • 数据丢失不敏感的情况下,可以关掉持久化功能,这样的话redis和memcached差别就不那么大了,甚至memcached的吞吐还要好于redis。
  • 可以主从部署,可以让一台redis作为写入,但不开启持久化,从redis只读数据,并且开始持久化。
  • 尽量使用混合持久化的方式。
  • Redis对cpu要求不高,但是内存和磁盘的快慢对redis影响很大,所以尽量用比较好的内存和磁盘。