数据库
redisDb结构中的dict字典保存了数据库中的所有键值对,我们将这字典成为键空间。
redisDb结构的expires字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典。
过期键删除策略
- 定时删除:在设置键的过期时间的同时,创建一个定时器。让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 优点:过期键尽快删除,释放占用内存
- 缺点:CPU时间最不友好,在内存不紧张但CPU时间紧张的情况下,将cpu用在删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。此外,时间事件通过无序链表实现,查找时间复杂度为O(N),并不高效。
- 惰性删除:放任键过期不管,但每次从键空间中获取键时,都检查取得的键是否过期。如果过期的话,就删除该键,如果没有过期,就返回该键。
- 优点:CPU时间最友好,不会在删除无关过期键上花费任何cpu时间
- 缺点:内存不友好,过期键不被访问到,也许永远就不会被删除
- 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则有算法决定。
- 定期删除策略是前两种策略的一种整合和折中。根据情况,合理的设置删除操作执行时长和执行频率。
AOF、RDB和复制功能对过期键的处理
RDB
- 生成文件:在执行Save命令或BgSave命令创建一个新的Rdb文件时,会对数据库中键进行检查,已过期的键不会保存到Rdb文件中。
- 载入文件:以主服务器模式运行,未过期的键会被载入到数据库中。过期键会被忽略。以从服务器模式运行,所有键不论是否过期,都会被载入到数据库(主从服务器进行数据同步时,从服务器数据库就会被清空。过期键对载入文件的从服务器也不会造成影响)
AOF
- 文件写入:当过期键没有被删除,那么Aof文件不会因为这个键产生任何影响。当被删除后,会追加一条DEL命令,显式记录该键已被删除
- 重写:重写过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中。
复制
当服务器运行在复制模式下,从服务器的过期键删除动作由主服务器控制。
从服务器会正常返回键值给客户端,好像键没有过期一样(这是个bug吧)
主服务器被访问,删除该过期键,向客户端返回空回复,并向从服务器发送DEL命令。从服务器才删除
RDB持久化
RDB持久化功能生成的文件是一个经过压缩的二进制文件。
- SAVE命令:阻塞Redis服务器进程,直到创建完毕。阻塞期间,服务器不能处理任何命令请求
- BGSAVE命令:派生出一个子进程负责创建RDB文件,服务器进程继续处理命令请求。bgsave期间,客户端发送的save、bgsave命令会被服务器拒绝。bgrewriteaof命令会被延迟到bgsave执行完毕。
Rdb文件的载入是在服务器启动时自动执行的,所以没有用于载入Rdb文件的命令。如果服务器开启了AOF持久化功能,服务器会优先使用AOF文件来还原数据。
保存条件
用户可以通过save设置多个保存条件,只要其中任意一个条件被满足,服务器就会执行bgsave命令
如: save 900 1 服务器在900秒内,对数据库进行了至少1次修改
通过saveparams数组,dirty计数器(修改次数),lastsave属性(上一次执行时间),周期性操作函数serverCron默认每隔100毫秒就会执行一次,其中一项工作就是检查save选项所设置的保存条件是否满足。满足,执行bgsave命令。
AOF持久化
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。它的实现可以分为命令追加、文件写入、文件同步三个步骤。
命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。
AOF文件写入
通过flushAppendOnlyFile函数,考虑是否将aof_buf缓冲区中内容写入和保存到AOF文件里面。函数行为由appendfsync配置项决定。默认everysec
- always:效率最慢,但也是最安全的。即使故障停机,也只会糗事一个事件循环中所产生的命令数据
- everysec:从效率讲,足够快,就算故障停机,也只丢失一秒钟的命令数据。
- no:无须执行同步操作(大部分情况下),Aof文件写入速度是最快的。但单次同步时长通常是三种模式中最长的。故障停机时,将丢失上次同步AOF文件之后的所有写命令数据。
文件同步
为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区空间被填满,或者超过指定时限后,才真正将缓冲区中数据写入到磁盘里面。
AOF文件重写
AOF文件重写并不需要对现有AOF文件进行任何读取、分析或者写入操作,是通过读取服务器当前的数据库状态来实现的。
实现原理:从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令。
如果服务器对animals键执行了以下命令:
SADD animals "cat" // {"cat"}
SADD animals "dog" "panda" "tiger" // {"cat","dog","panda","tiger"}
SREM animals "cat" // {"dog","panda","tiger"}
SADD animals "lion" "cat" // {"dog","panda","tiger","lion","cat"}
为了记录animals键的状态,AOF文件必须保存上面四条命令。AOF重写,通过读取animals键的值,用一条
SADD animals "dog" "panda" "tiger" "lion" "cat" 来代替上面的四条命令。
注意:实际中,为了避免在执行命令时造成客户端输入缓冲区异常,在处理列表、哈希表、集合、有序集合时,会先检查元素数量,超过REDIS_AOF_REWRITE_ITEMS_PER_CMD常量(默认64),使用多条命令来记录键的值。
AOF后台重写
AOF重写程序放到子进程执行
- 子进程重写期间,服务器进程可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性
为了解决重写期间数据未写入新AOF文件的数据不一致问题,Redis服务器设置了AOF重写缓冲区。
当子进程完成AOF重写后,会向父进程发送一个信号,父进程接到信号后,执行以下工作:
- 将AOF重写缓冲区中的所有内容写入到新AOF文件中
- 对新的AOF文件进行改名,原子地覆盖现有AOF文件,完成新旧两个文件的替换