前言:此文章是学习Redis 核心技术与实战时做的学习笔记,仅供学习和参考。
Redis用作缓存时,数据存储在内存中。一旦服务器宕机,内存中的数据就会全部丢失。
一个解决方案是从数据库中恢复数据,但是存在两个问题:1、数据库中的数据是存储在硬盘中的,存取速度相对于内存来说太慢,大大影响了Redis的性能;2、频繁读取数据会给数据库增加压力;
因此Redis拥有自己的持久化机制,即 AOF(Append Only File)日志和 RDB 快照。
Redis执行的是写后日志,即先执行命令,把数据写入内存,然后才记录日志。
以 Redis 收到“set testkey testvalue”命令后记录的日志为例,看看 AOF 日志的内容。
“3”表示当前命令有三个部分,
每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3 set”表示这部分有 3 个字节,也就是“set”命令。
1、Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以写后日志可以避免出现记录错误命令的情况,因为能够被Redis执行的命令肯定是正确的命令。
2、它是在命令执行后才记录日志,所以不会阻塞当前的写操作。
AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。
Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
No,**操作系统控制的写回:**每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择 Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。
采用AOF 重写机制,重写就是是根据这个键值对当前的最新状态,为它生成对应的写入命令。比如对一个键进行了多次值更新,原AOF文件中就会记录很多条命令,重写只需要根据最后一次更新的数据,生成相应的写入命令即可,如下图:
LPUSH u:list “N”, “C”, "D"这一条命令就能实现该数据的恢复,这就**节省了五条命令的空间。**对于被修改过成百上千次的键值对来说,重写能节省更大空间。
AOF 日志由主线程写回不同,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。
一个拷贝
fork子进程时,子进程是会拷贝父进程的页表,即虚实映射关系,而不会拷贝物理内存。子进程复制了父进程页表,也能共享访问父进程的内存数据了,此时,类似于有了父进程的所有内存数据。
“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据
两处日志
第一处日志就是指正在使用的 AOF 日志,如果此时有新的写操作,Redis 会把这个操作写到它的缓冲区;
第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作
因为AOF日志记录的是操作命令,而不是实际的数据,所以,用 AOF 方法进行故障恢复的时候,需要**逐一把操作日志都执行一遍。**如果操作日志非常多,Redis 就会恢复得很缓慢,影响性能。
RDB内存快照。所谓内存快照,就是指内存中的数据在某一个时刻的状态记录,把某一时刻的状态以文件的形式写到磁盘上。
和 AOF 相比,RDB 记录的是某一时刻的数据,并不是操作,所以,在做数据恢复时,我们可以直接把 RDB 文件读入内存,很快地完成恢复。
Redis使用的是全量快照,即把内存中的所有数据都记录到磁盘中
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
save:在主线程中执行,会导致阻塞;
bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
使用 bgsave 可以避免阻塞,但是避免阻塞和正常处理写操作并不是一回事。此时,主线程的确没有阻塞,可以正常接收请求,但是,为了保证快照完整性,它只能处理读操作,因为不能修改正在执行快照的数据。
Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
两次快照之间的时间间隔很重要,间隔时间很短,即使某一时刻发生宕机了,因为上一时刻快照刚执行,丢失的数据也不会太多但是如果如果频繁地执行全量快照,也会带来两方面的开销。
一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,容易造成恶性循环。
另一方面,**fork 这个创建过程本身会阻塞主线程,**而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了(所以,在 Redis 中如果有一个 bgsave 在运行,就不会再启动第二个 bgsave 子进程)。
增量快照,所谓增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
增量快照前提是要记住哪些数据被修改了。需要我们使用额外的元数据信息去记录哪些数据被修改了,这会带来额外的空间开销问题。
为了“记住”修改,引入的额外空间开销比较大。这对于内存资源宝贵的 Redis 来说,有些得不偿失。
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,就不会出现文件过大的情况了,也可以避免重写开销。