第 7 章 压缩列表
1. 概念:
- 压缩列表是列表键和哈希键的底层实现之一。
- 当一个列表键只包含少量列表项,并且每个列表项是小整数值或长度比较短的字符串,那么Redis就会使用压缩类别来做列表键的底层实现。
- 哈希键里面包含的所有键和值都是最小整数值或短字符串。
2. 压缩列表的构成:
- 压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的书序结构。
3. 压缩列表节点的构成:
4. 连锁更新:
每个节点的previous_entry_length属性记录了前一个节点的长度:如果前一个字节长度小于254字节,那么previous_entry_length属性需要用1字节空间保存这个值,如果前一个字节的长度大于或等于254字节,那么previous_entry_length属性需要5字节的空间保存这个值。
如果在压缩列表中新添加一个长度大于等于254字节的节点,导致后边所有节点多次空间拓展的操作称为“连锁更新”。
5. 其他:
- 压缩列表是一种为了节约内存而开发的顺序型数据结构。
- 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或整数值。
- 添加节点或者删除节点可能或引发连锁更新操作,但是这种操作出现的几率不高。
第 8 章 对象
1. 概念:
- Redis没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。
- 我们可以针对不同的使用场景,为对象设置不同的数据结构,从而优化对象在不同场景下的使用效率。
- Redis对象系统基于引用计数计数的内存回收机制,可以有效节约内存。‘
- Redis的对象带有访问时间记录信息,该信息可用用于计算数据库键的空转时间,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能优先被服务器删除。
2. 对象类型:
3. 对象结构:
不同类型值对象的 Type命令的输出:
不同类型和编码的对象:
Object encoding对不同编码的输出:
4. 各种对象的编码:
- 字符串对象:
编码可以是int、raw或者embstr。
如果字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,象编码设置为int。
如果字符串对象保存是一个字符串值,并且这个字符串的长度大于32字节,使用SDS保存这个字符串,并且对象编码设置为raw。
如果字符串对象保存是一个字符串值,并且这个字符串的长度小于等于32字节,使用SDS保存这个字符串,并且对象编码设置为embstr。
long double 类型表示的浮点数,是转换成字符串保存的,对象编码为embstr。
int编码字符串对象不是整数值时,会int->raw
embstr编码字符串对象执行任何修改时,会embstr->raw。
- 列表对象:
编码可以是ziplist或者linkedlist。
满足所有字符串元素的长度都是小于64字节,并且保存元素数量小于512个时使用ziplist,否则使用linkedlist。
- 哈希对象:
编码可以是ziplist或者hashtable。
满足所有字符串元素的长度都是小于64字节,并且保存元素键值对数量小于512个时使用ziplist,否则使用hashtable。
- 集合对象:
编码可以是intset(整数集合)或者hashtable。
满足对象保存的所有元素都是整数值,并且保存对象的元素数量不超过512个使用intset,否则使用hashtable。
- 有序集合对象:
编码可以是ziplist或者skiplist。
满足所有字符串元素的长度都是小于64字节,并且保存元素数量小于128个时使用ziplist,否则使用skiplist。
5. 内存回收:
C语言并不具备内存回收功能,所以Redis在对象系统中构建了一个引用计数技术来实现内存回收机制。
6. 对象共享:
对象的引用计数属性还带有对象共享的作用。
Redis只对包含整数值的字符串对象进行共享,即共享值为0到9999的字符串对象。
7. 对象的空转时长:
redisObject结果包含最后一个属性lru属性,该属性记录了对象最后一次被命令程序访问的时间。空转时长=当前时间-lru的值。
第 9 章 数据库
1. 读写键空间时的维护操作:
- 在取读一个键之后(读操作和写操作都要对键进行取读),服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数;
- 在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间;
- 如果服务器在读取一个键时发现该键已经过期,那么服务器会先刑除这个过期键,然后在执行余下其他操作;
- 如果有客户端使用 WATCH命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为脏( dirty),从而让事务程序注意到这个键已经被修改;
- 服务器每次修改一个键之后,都会对脏( dirty)键计数器的值增1,这个计数器会触发服务器的持久化以及复制操作;
- 如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。
2. 过期键删除策略:
- 定时删除:在设置键的过期时间的同时,创建一个定时器( timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检査取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
- 定期删除:每隔一段时间,程序就对数据库进行一次检査,删除里面的过期键。至于要删除多少过期键,以及要检査多少个数据库,则由算法决定。
在这三种策略中,第一种和第三种为主动删除策略,而第二种则为被动刑除策略。
3. Redis的过期键删除策略:
redis使用惰性删除和定期删除策略:通过配合使用这两种策略,服务器可以很好地合理使用CPU时间和避免浪费内存空间之间确定平衡。
4. AOF、RDB和复制功能对过期键的处理:
- RDB
生成RDB文件时(使用save或bgsave命令),不会将过期键存入RDB文件中;
载入RDB文件时,如果服务器以主服务器运行,忽略过期键,如果服务器以从服务器运行,过期键会正常载入,但主从同步时候过期键会被清空。
- AOF
AOF写入时,如果键过期,但还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生影响,当过期键被惰性删除或者定期删除后,程序会向AOF文件追加一条DEL命令。
AOF重写,过期的键不会被保存重写到AOF的文件中。
- 复制
当服务器运行在复制模式下时,从服务器的过期键删除动作是由主服务器控制:
i. 主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个DEL命令,告诉从服务器删除这个过期键。
ii. 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续想处理未过期键一样处理过期键
iii. 从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键。
5. 重点回顾:
- Redis服务器的所有数据库都保存在 redisServer.db数组中,而数据库的数量则由 redisServer. dbnum属性保存;
- 客户端通过修改目标数据库指针,让它指向 redisServer.db数组中的不同元素来切换不同的数据库;
- 数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires字典则负责保存键的过期时间。
- 因为数据库由字典构成,所以对数据库的操作都是建立在字典操作之上的。
- 数据库的键总是一个字符串对象,而值则可以是任意一种 Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象和有序集合对象,分别对应字符串键、哈希表键、集合键、列表键和有序集合键。
- expires字典的键指向数据库中的某个键,而值则记录了数据库键的过期时间,过期时间是一个以毫秒为单位的UNIX时间。
- Redis使用惰性删除和定期删除两种策略来删除过期的键:惰性删除策略只在碰到过期键时才进行删除操作,定期删除策略则每隔一段时间主动查找并删除过期键。
- 执行SAVE命令或者BGSAVE命令所产生的新RDB文件不会包含已经过期的键。
- 执行BGREWRITEAOF命令所产生的重写AOF文件不会包含已经过期的键。
- 当一个过期键被删除之后,服务器会追加一条DEL命令到现有AOF文件的末尾,显式地删除过期键。
- 从服务器即使发现过期键也不会自作主张地删除它,而是等待主节点发来DEL命令,这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。
- 当 Redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知。
第 10 章 RDB持久化
1. RDB文件的创建:
Save和Bgsave命令可以用于生成RDB文件。
2. Save和Bgsave命令的区别:
- Save命令会阻塞Redis服务器进程,知道RDB文件创建完毕为止,在服务器进程阻塞期间,服务器器不能处理任何命令请求。
- Bgsave命令会派生出一个子进程,然后由子进程负责创建RDB文件,父进程继续处理命令。
3. RDB文件的载入:
- RDB文件的载入是在服务器启动时自动执行的,所有Redis并没有专门用于载入RDB文件的命令。
- 如果服务器开启了AOF持久化功能,服务器会优先使用AOF文件来还原数据库状态(AOF文件的更新频率比RDB文件更新频率高)。
- 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。
- 载入RDB文件的实际工作由rdb.c/rdbLoad函数完成。
- RDB文件的载入时服务器状态是阻塞状态。
4. 在Bgsave命令执行期间,服务器处理Save、Bgsave、Bgrewriteaof三个命令的方式会有所不同:
- BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,为了避免父进程(服务器进程)和子进程同时执行两个rdb Save调用,防止产生竟争条件。
- BGSAVE命令执行期间,客户端发送BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竟争条件。
- BGREWRITEAOF和BGSAVE两个命令不能同时执行(避免两个子进程执行大量的磁盘写入):如果BGSAVE命令正在执行,客户端发送的 BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行;如果BGREWRITEAOF命令正在执行,客户端发送的BGSAVE命令会被服务器拒绝。
5. Bgsave支持自动间隔性保存:
6. dirty计数器和lastsave属性:
- dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、刑除、更新等操作)。
- lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。
7. RDB文件结构:
- RBD文件的最开头是REDIS部分,这个部分长度为5字节,保存这个“REDIS”五个字符。
- db_version长度为4字节,它的值是一个字符串表示的整数,这个整数记录了RDB的版本号("0006"代表RDB文件版本为第六版本)。
- datebase部分包含多个数据库,以及各个数据库中的键值对数据。如果所有数据库的状态为空,那么这部分长度为0字节。
- EOF常量为1字节,这个常量标志着RDB文件正文内容结束。
- check_sum是一个8字节长的无符号整数,保存着一个校验和,这个校验和是通过对REDIS、db_version、database、EOF四个部分内容进行计算得出来的。
带有两个非空数据库的结构:
每个非空数据库都保存SELECTDB、db_number、key_value_pairs三个字段:
- SELECTDB常量的长度为1字节,用来告诉程序接下来要读入数据库号码。
- db_number保存着一个数据库号码。
- key_value_pairs部分:不同类型type,又对应不同数据结构的value
8. 分析RDB文件:
参考教材P133
9. 重点回顾:
- RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据。
- SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。
- BGSAVE令由子进程执行保存操作,所以该命令不会阻塞服务器。
- 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行 BGSAVE命令。
- RDB文件是一个经过压缩的二进制文件,由多个部分组成。
- 对于不同类型的键值对,RDB文件会使用不同的方式来保存它们。
第 11 章 AOF持久化
1. 流程:
2. AOF持久化的实现:
- AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
- 追加-->将客户端请求命令追加的缓冲区,写入与同步-->将客户端请求命令从缓存区持久化到AOF文件中。
3. AOF文件的写入与同步:
- Redis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需求的定时运行函数。
- 服务器配置的appendfsync选项值(always、everysec、no)来决定AOF写入与同步的方式,它决定了AOF持久化的效率和安全性。
- appendfsync选项带来的数据丢失问题:always不会丢失,但是效率低;everysec默认使用,会丢失1秒钟数据;no丢失数据量缓存中的数据,同步时间由操作系统决定。
4. AOF文件的载入与数据还原:
5. AOF重写:
- 作用:解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写(rewrite)功能。
- 命令:Bgrewriteaof,后台重写
- 实现:直接取读服务器当前数据库的状态来实现的,不是通过对现有的AOF文件进行任何读取、分析或者写入操作。
- Tips:为了避免执行命令时,造成客户端输入缓冲区溢出,重写程序处理列表、哈希表、集合、有序集合这四种类型带有多个值事,先会检查元素数量,如果超过REDIS_AOF_REWRITE_ITEMS_PER_CMD(默认64个)常量的值,就会使用多条命令
6. 重点回顾:
- AOF文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
- AOF文件中的所有命令都以Redis命令请求协议的格式保存。
- 命令请求会先保存到AOF缓冲区里面,之后再定期写入并同步到AOF文件。
- appendfsync选项的不同值对AOF持久化功能的安全性以及Redis服务器的性能有很大的影响。
- 服务器只要载入并重新执行保存在AOF文件中的命令,就可以还原数据库本来的状态。
- AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
- AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写人操作。
- 在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。