因为Redis是内存数据库,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。因此Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。

Redis提供了两种持久化的方式——RDB 持久化和AOF持久化

RDB持久化

Redis通过save或者bgsave命令来生成RDB文件,完成数据的持久化。在加载RDB文件的时候,服务器会一直处于阻塞状态,直到载入工作完成为止。

save命令会阻塞Redis服务器进程,直到RDB文件创建完成为止,服务器在阻塞期间,不处理任何请求。

bgsave命令则会fork一个子进程,由子进程来负责创建RDB文件,将数据写入到临时文件当中,在写完临时文件后,替换旧的RDB文件;而服务器进程则继续处理收到的命令请求。

  • 自动间隔性保存
    Redis 允许用户在配置文件中,通过save选项来配置多个保存条件,只要有一个条件满足,服务器就会执行bgsave命令。
save 900 1 // 服务器在900秒之内,对数据库进行了至少1次修改。
save 300 10 // 服务器在300秒之内,对数据库进行了至少10次修改
save 60 10000 // 服务器在60秒之内,对数据库进行了至少10000次修改。

服务器中维持着一个dirty计数器,以及一个lastsave属性:

  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。
  • lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。

服务器通过这两个内容来检查保存条件是否满足,判断是否执行bgsave命令。

AOF持久化

除了RDB以外,Redis还提供了AOF持久化功能。RDB持久化是将数据库中的键值对写入到RDB文件中,而AOF持久化不同,AOF将服务器执行的命令写到了AOF文件当中。此外,RDB文件是一个压缩的二进制文件,而AOF文件是以纯文本的格式保存,因此我们可以直接阅读AOF文件。

在加载AOF文件的时候,服务器会创建一个伪客户端(fake client),由伪客户端逐条读取aof文件中的写命令。

  • AOF的实现
    AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
    追加:开启AOF持久化的功能后,服务器每执行一个写命令之后,都会将这个写命令追加到aof_buf缓冲区的末尾。
    文件写入与同步:服务器每次结束一个事件循环之前,它都会调用 flushAppendOnlyFile,考虑是否需要将aof_buf缓冲区中的内容写入和保存到aof文件里面。flushAppendOnlyFiled 的行为根据配置文件appendfsync来决定。
  • always:将 aof_buf缓冲区的所用内容写入到 aof文件,并立刻同步到磁盘中。
  • everysec:将aof_buf缓冲区的所用内容写入到aof文件,如果上次同步aof文件的时间距现在超过了一秒钟,则将aof文件的内容同步到磁盘中。
  • no:只是将aaof_buf缓冲区的所用内容写入到aof文件,由操作系统决定何时同步到磁盘中。
  • AOF重写
    为了解决aof不断追加命令导致aof文件过大的问题,redis提出了aof重写的机制。
    Redis服务器可以创建一个新的aof文件来替代现有的aof文件,新旧两个aof文件所保存的数据库状态相同,但新aof文件不会包含任何浪费空间的冗余命令,所以新aof文件的体积通常会比旧aof文件的体积要小得多。
    AOF后台重写( bgrewirtaof命令):如果 Redis 服务器直接调用 aof 重写函数的话,会导致在重写期间,服务器无法处理客户端发来的命令请求。因此Redis采用了 aof 后台重写的机制。简单来说就是,Redis 将 aof 重写程序放到子线程中来执行,同时将期间新的写命令加入到 aof重写缓冲区中,重写完成后,将aof重写缓冲区中的内容追加到新的aof文件中,最后用新的aof文件替换旧的aof文件
  • 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。
  • 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。

数据库

Redis服务器将所有数据库都保存在服务器状态 redisServer结构的db数组中,db数组的每个项都是一个 redisDb结构,每个 redisDb结构代表一个数据库。Redis默认设置中有16个数据库,客户端可以通过命令 select 来切换数据库。

Redis是一个键值对数据库服务器,服务器中的每个数据库都由一个 redisDb结构表示,其中, redisDb结构的 dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间。通过指令 set来添加键值对,del删除键值对,set更新键值对,get获取key对应的value。

  • 过期删除策略
    通过 EXPIRE或者 PEXPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间(TTL),在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键。 SETEX命令可以在设置一个字符串键的同时为键设置过期时间,其原理与 EXPIRE设置过期时间的原理是完全一样的。
    <aside> 💡 expire<key><ttl>命令用于将键key的生存时间设置为ttl秒。 pexpire<key><ttl>命令用于将键key的生存时间设置为ttl毫秒。 expireat<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的秒数时间戳。 pexpireat<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的毫秒数时间戳。
    </aside>
    Redis数据库采用了一种 dict字典结构(expiresDict)来保存数据库中所有键的过期时间,我们称这个字典为过期字典。
  • 过期字典的键是一个指针,这个指针指向键空间中的某个键对象。
  • 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间。

Redis采用的过期键删除策略为两种,惰性删除和定期删除

**惰性删除:**程序只会在取出键的时候才对键进行过期检查,且删除目标仅限于当前处理的键。这是一种对CPU时间友好但是对内存极其不友好的策略,可能导致无用的垃圾数据占用了大量的内存。

**定期删除:**每个一段时间执行一次删除过期键的操作,从数据库的 expires字典中随机检查一部分键的过期时间,并删除其中的过期键。通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响,也有效地减少了因为过期键而带来的内存浪费。

  • AOF、RDB和复制功能对过期键的处理
    RDB: 在执行 save命令或者 bgsave命令创建新的 RBD文件的时候,服务器会对数据库中的键进行检查,已经过期的键不会被保存到新创建的 RBD文件中;而在载入 RDB文件的过程中,主服务器会主动删除过期的键,只加载未过期的键而从服务器则会将 RDB中所有的键都进行加载。
    **AOF:**当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响;若过期的键被删除后,会在AOF文件的结尾显式地添加一个 DEL语句,将这个键删除。
    AOF重写:AOF重写的过程中会对键是否过期进行检查,与生成RDB文件类似。因此过期的键对AOF重写没有任何影响。
    复制:当服务器处于主从复制的模式下,从服务器对过期键的处理由主服务器进行控制。当一个过期的键在从服务器被访问的时候,从服务器会将过期的键返回,而在主服务器进行访问的时候,主服务器会删除过期键并且发送DEL命令给从服务器。