Redis作为一个内存数据库数据都存储在内存中,如果服务器重启那么内存中的数据将会丢失。如果Redis作为缓存丢失了数据没关系,可以重新加载;但如果是作为数据库,数据是绝对不能丢失的。那么存储层的持久化无非两种,一种是快照/副本,另外一种就是日志。Redis同样为了避免数据丢失提供了不同级别的的持久化方式,分别是RDB和AOF。下面我们对两种持久化方式分析理解。

RDB

RDB是一种全量的数据快照,是能够在指定时间间隔对你的数据进行快照存储,默认的存储文件是dump.rdb 。在Redis服务重启时,会加载该文件将数据恢复到内存中。

使用

RDB使用的方式有三种。第一种是使用save命令生成dump.rdb 文件;第二种是使用bgsave 命令生成dump.rdb 文件;第三种则是在配置文件中进行配置条件触发生成dump.rdb文件。

  • save命令

客户端输入save命令进行持久化请求是一种同步方式。这种方式的缺点在于当执行该命令时,Redis服务端会阻塞其他写请求直至完成同步。因此不建议在生产环境使用,因为若数据量较大,同步则会耗时很久。常用在后续不会再接收写请求时使用,例如服务停机维护等场景。
image.png

  • bgsave命令

bgsave 命令看起来和save 命令很相似,但bgsave命令是一种异步方式。这种方式不同于save的是Redis会forks出一个子进程进行数据持久化,父进程继续接收请求,当执行完毕同步子线程关闭。
image.png

  • 配置文件触发

配置文件(redis.conf)中配置条件触发生成dump.rdb文件实际上也是执行bgsave 命令,换句话说就是 配置触发bgsave的规则,我们看看默认的规则:

## 900秒至少1次写的命令
save 900 1
## 300秒至少10次写的命令
save 300 10
## 60秒至少10000次写的命令
save 60 10000

这种方式的缺点就是如果时间间隔控制太大会造成数据丢失,但太小又会导致频繁持久化,影响性能。
配置文件除了触发配置,rdb文件还存在一些基础配置,如下所示:

## 是否压缩rdb文件
rdbcompression yes
## 如果为yes则表示,当备份进程出错的时候,主进程就停止进行接受新的写入操作
## 这样是为了保护持久化的数据一致性的问题。
stop-writes-on-bgsave-error yes
## rdb文件的名称(单机不同端口启动服务器)
dbfilename dump.rdb
## rdb文件保存目录
dir /path/to/dump

工作方式

当 Redis 需要保存dump.rdb 文件时, 服务器执行以下操作:

  • Redis 调用forks同时拥有父进程和子进程。
  • 子进程将数据集写入到一个临时 RDB 文件中。
  • 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。

Copy-On-Write:如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给调用者,而其它调用者所见到的最初的资源仍然保持不变

优点

  • RDB文件非常紧凑,它保存了某个时间点的数据集,适合数据备份;
  • 使用fork出子进程,可以最大程度的使用redis的性能;
  • RDB持久化类似java中的序列化,恢复速度相比AOF来的快。

    缺点

  • 会造成数据丢失,因为在两个存储时间点A和B之间Redis宕机,那么这个时间窗口内的数据就会因为还没来得及存储而造成丢失;
  • bgsave 命令需要fork出子进程,当数据集较大时,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。

AOF

AOF是一种增量的形式追加记录每次对服务器写的操作,默认的存储文件appendonly.aof,是当服务器重启的时候会重新执行这些命令来恢复原始的数据。
image.png

注意这里并不是直接存储Redis的写命令,而是这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。

使用

AOF的使用需要在配置文件中开启,默认是不开启AOF持久化的方式,配置文件关于AOF的配置如下所示:

## 是否开启AOF yes为开启 no为不开启
appendonly yes

## aof文件的名称
appendfilename appendonly.aof

## 三种写入策略
## always 每一个写请求写入AOF文件 即时将缓冲区内容写入AOF文件
appendfsync always
## everysec 每隔一秒写入AOF文件 每隔一秒将缓冲区内容写入AOF文件 默认策略
appendfsync everysec
## no 不负责写入AOF文件 交由操作系统决定 
## 一般来说,操作系统考虑效率问题,会等待缓冲区被填满再将缓冲区数据写入AOF文件中
appendfsync no

优点

  • Redis可以使用三种不同的fsync策略,数据不容易丢失。
  • AOF文件是一个只进行追加的日志文件,对服务器性能影响小。

    缺点

  • 对于相同的数据集,文件的体积AOF要大于RDB且由于不断追加命令会致使AOF文件过大;
  • AOF恢复数据的速度相比于RDB慢。

    AOF重写

    因为AOF不断追加命令导致AOF文件过大,为了解决这种问题所以Redis支持可以在不打断客户端的情况下,对AOF进行重写,假设你对一个key increment了100次,实际上命令就可以重写为 set key 100。那么Redis对AOF重写有两种方式。

  • 在配置文件中配置是否开启重写,同样可以配置基于什么情况重写

    ## 默认不重写aof文件
    no-appendfsync-on-rewrite no
    ## aof自动重写文件的百分比
    auto-aof-rewrite-percentage 100
    ## aof自动重写文件的最小容量
    auto-aof-rewrite-min-size 64mb
  • bgrewriteaof 命令 (客户端输入)

AOF重写也是异步操作,同样Redis主进程forks出一个子进程来处理。重写的过程同样用到了写时复制(copy-on-write)机制,过程如下:

  • Redis 执行 fork() ,现在同时拥有父进程和子进程。
  • 子进程开始将新 AOF 文件的内容写入到临时文件。
  • 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
  • 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾,最后使用新的AOF文件替换掉旧的AOF文件。

    AOF文件损坏

    在写入AOF文件时,Redis服务器由于某些原因宕机则AOF文件会出现错误, 在重启时Redis会拒绝载入AOF文件,因此确保数据一致性,可以使用redis-check-aof 来修复这些问题。

    redis-check-aof -fix file.aof
    tips:备份原先的AOF文件,以防万一。(处理任何操作时最好都先备份)

RDB和AOF混合持久化

那么RDB和AOF都有各自的优点和缺点,那么如何选择比较合适呢?RDB的优点是文件体积小,恢复数据速度快,但容易丢失数据;而AOF的优点是不易丢失数据,但文件体积过大。那么如果结合这两者的优点能够给出一个方案:既保证RDB文件体积小的前提下也能保证AOF的不容易丢失数据是不是就能鱼和熊掌兼得呢?我们前面也在介绍RDB和AOF的过程中也分别强调了RDB是全量备份,AOF是增量备份。那么在Redis4.0之后提供这种持久化的方式,并且此方式为默认方式使用。

RDB-AOF方式下,持久化策略首先将缓存中数据以RDB方式全量写入文件,再将写入后新增的数据以AOF的方式追加在RDB数据的后面,在下一次做RDB持久化的时候将AOF的数据重新以RDB的形式写入文件。这种方式既可以提高读写和恢复效率,也可以减少文件大小,同时可以保证数据的完整性。在此种策略的持久化过程中,子进程会通过管道从父进程读取增量数据,在以RDB格式保存全量数据时,也会通过管道读取数据,同时不会造成管道阻塞。可以说,在此种方式下的持久化文件,前半段是RDB格式的全量数据,后半段是AOF格式的增量数据。

过期key的淘汰策略

Redis除了持久化数据到磁盘之外,那么长期在内存中的数据是如何维护清理的呢?通常过期策略如下三种。

  • 定时删除(主动行为)

用定时器来监视设置了过期时间的key,过期则删除该key,这样对内存友好但十分消耗CPU资源,影响缓存的吞吐量和响应时间。

  • 惰性删除(被动行为)

当访问一个key时,判断该key是否过期,若过期则删除。该策略节省CPU资源但对内存不友好。有可能存在过期key没有被访问一直存在内存中的情况。

  • 定期删除

一定时间间隔内随机扫描数据库一定数量(非全部)带过期时间的key,删除其中已经过期的key。但该方案又会存在很多过期的key如果没有被随机到则会继续保留在内存中,所以Redis中采用了 惰性删除 + 定期删除的方案,这样在一定程度上将CPU和内存的性能达到平衡。
Redis具体做法可在其官方文档如下:

Specifically this is what Redis does 10 times per second:

  1. Test 20 random keys from the set of keys with an associated expire.
  2. Delete all the keys found expired.
  3. If more than 25% of keys were expired, start again from step 1.

内存回收策略

假设Redis在不断加入新的key,但是之前的key也还没有到达过期时间,随着key的越来越多,内存的空间已经不够,那么Redis会怎么做呢?
首先,你可以通过在Redis的配置文件中指定maxmemory 来指定内存大小,或在运行中通过config set 动态设置内存大小。

## 设置redis存放数据的最大的内存大小
maxmemory <bytes>
对于64 bit的机器,如果maxmemory设置为0,那么就默认不限制内存的使用,直到耗尽机器中所有的内存为止; 但是对于32 bit的机器,有一个隐式的闲置就是3GB。

其次,如果内存已经达到你的最大限制,那么可以在Redis的配置文件中使用maxmemory-policy 来配置内存回收策略。

## 配置redis回收策略
maxmemory-policy noeviction

Redis总共提供了6种配置策略的方式,如下所示

  • volatile-lru: 在所有带有过期时间的 key 中使用 LRU 算法淘汰数据;
  • alkeys-lru: 在所有的 key 中使用 LRU 算法淘汰数据,保证新加入的数据正常;
  • volatile-random: 在所有带有过期时间的 key 中随机淘汰数据;
  • allkeys-random: 在所有的 key 中随机淘汰数据;
  • volatile-ttl: 在所有带有过期时间的 key 中,淘汰最早会过期的数据;
  • noeviction: 不回收,当达到最大内存的时候,在增加新数据的时候会返回 error,不会清除旧数据,这是 Redis 的默认策略;

注:这里的LRU算法不是一种精确的LRU算法,而是通过采样的近似的LRU算法,在配置文件中可以通过maxmemory-samples 来设置采样大小,默认值为5。官方提供的采用对比如下:
03.png
为什么不采用实际的LRU算法呢,官方文档给出了答案。

The reason why Redis does not use a true LRU implementation is because it costs more memory.

参考链接

Redis官方中文文档-持久化
Redis由浅入深深深深深剖析
10分钟彻底理解Redis的持久化机制:RDB和AOF
一文看懂 Redis 的内存回收策略和 Key 过期策略


Ekko
2 声望0 粉丝