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