一、redis数据库
1、键空间
redisDb结构中的字典dict,保存着数据库中所有的键值对,称之为键空间。键空间的键即为数据库的键,键空间的值即为数据库的值,对redis数据库的添加、删除、更新、查找等操作,实际上是对键空间这个字典的增删改查操作。
2、过期字典
redisDb结构中的字典expires,保存了数据库中所有键的过期时间,称之为过期字典。过期字典的键是指向键空间中的某个键对象,过期字典的值是一个long long类型的整数,保存了键所指向的数据库键的过期时间,其是一个精确到毫秒的UNIX时间戳。
所有为redis键设置过期时间、生存时间的操作,最终都会转化为PEXPIREAT命令,其可以为一个键设置毫秒级的过期时间。
3、过期键删除策略
a、定时删除
在设置键的过期时间的同时设置一个定时器,当过期时间到来时进行删除。
虽然对内存最友好,但是对CPU时间压力很大,需要创建大量的定时器,会影响到服务器的响应时间和吞吐量。
b、惰性删除
过期键放任不管,当每次从键空间读取键值时,都检查是否过期,如果过期则删除掉。
不需要额外花费CPU时间,但是对内存会造成很大的浪费,如果一个键一直不被访问,则其一定不会被删除。
c、定期删除
每隔一段时间,程序就对数据库进行一次检查,删除掉过期的键。
可以避免上述问题,但是需要控制删除操作执行的时长和频率来降低对CPU和内存的影响。
d、redis服务器的过期删除策略
采用了定期删除和惰性删除两个配合使用的策略。
e、主从服务器
当主服务器删除一个过期键后,他会向所有的从服务器发送一条DEL命令,显式地删除过期键;
当从服务器发现过期键后,不会进行删除,而是等待主服务器发来的DEL命令,这种统一、中心化的过期键删除策略可以保证主从服务器的数据一致性。
二、RDB持久化
RDB持久化可以在一定的触发条件下,将redis在内存中的数据库状态,即所有的键值对数据,保存在磁盘里面,避免数据的丢失。
1、RDB文件载入
只要redis服务器启动时检测到有RDB文件的存在,就会自动载入RDB文件;
但是如果开启了AOF持久化功能,由于AOF同步时效性更好,服务器会优先使用AOF文件来还原数据库的状态;
2、SAVE和BGSAVE
SAVE命令是由服务器进程直接执行保存操作,会阻塞整个服务器;
BGSAVE命令由子线程执行保存操作,不会阻塞服务器,但是在执行BGSAVE期间,所有的SAVE命令、BGSAVE命令都会被拒绝,BGREWRITEAOF命令会被延迟执行;
3、自动间隔保存
通过redis服务器的save选项,可以设置多个RDB的保存条件,只要有其中一个(比如300秒内发生了10次变更操作)被满足,即会触发BGSAVE命令;
dirty计数器记录了距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态进行了多少次修改(包括写入、删除、更新等);
lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE或者BGSAVE命令的时间;
redis服务器周期性操作函数serverCorn默认每隔100ms就会执行一次,该函数会检查服务器是否满足了在save选项中设置的保存条件,如果满足的话即会触发BGSAVE命令。
4、RDB文件结构
一个RDB文件由REDIS、db_version、database、EOF、check_sum,五个部分顺序排列组成;
REDIS部分是写死的这五个字符,目的是快速检查所载入的文件是否为RDB文件;
db_version长度为4个字节,值是一个字符串表示的整数,表示RDB文件的版本号;
database可以分为多个database,比如数据库0即为database0,每个数据库都可以保存为保存为SELECTDB、db_number、key_value_pairs三个部分;
其中SELECTDB常量长度为1字节,标示是这个database的开始,db_number则代表数据库号码,而key_value_pairs保存了数据库中所有键值对的数据;
EOF常量的长度为1字节,标示RDB文件正文内容的结束,当程序读入遇到这个值的时候,它能知道所有的键值对已经导入了;
check_sum是一个8字节长的无符号整数,保存着一个校验和,是通过对其他四个部分的内容进行计算得出的,以此来检验文件是否有出错和损坏。
三、AOF持久化
AOF持久化是通过保存redis服务器所执行的写命令来记录数据库的状态,文件中的所有命令都以redis命令请求协议的格式保存。
1、AOF的实现
可以分为追加(append)、文件写入、文件同步(sync)三个步骤
当AOF功能开启后,服务器会在执行完一个命令后,以协议的格式将执行的命令追加到服务器状态的aof_buf缓冲区末尾;
redis服务器周期性操作函数serverCorn在每次执行时,都会调用flushAppendOnlyFile函数,其考虑的是是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件中,具体行为由appendfsync选项决定;
0200926220731026.png)
2、AOF文件的载入与数据还原
由于AOF文件包括了重建数据库状态的所有命令,所以只要服务器将其读入并且重新执行,即可还原服务器关闭之前的状态,具体步骤如下:
a、创建一个不带网络链接的伪客户端;(由于redis的命令只能在客户端的上下文中执行,于是使用了伪客户端的形式来恢复数据库状态)
b、从AOF文件中分析并读出一条写命令;
c、使用伪客户端执行被读出的写命令;
d、一直执行步骤b和c,直到AOF中的写命令都被处理完毕。
3、AOF重写
如果不对文件加以控制,那么AOF文件中的内容会越来越多,体积过大可能对redis服务器造成影响,且使用文件来进行数据库还原的所需的时间就会越多。
为了解决AOF文件膨胀的问题,redis实现了AOF重写功能,服务器创建一个新的AOF文件来替换现有的AOF文件,新的文件不会包含任何浪费空间的冗余命令,其体积通常会小的多;
这个功能,是通过读取服务器当前的数据库状态来实现的,首先从数据库中读取键现在的值,然后用一条命令去记录键值对,替代之前记录这个键值对的命令,比如set a a,set a b,最终只会保存set a b命令。
AOF也是通过后台调用aof_rewrite函数来进行重写,但是在重写过程中,服务器还是可以处理新的命令请求,这个请求会带来保存数据的不一致。
所以redis除了维护一个AOF缓冲区外,还维护了一个AOF重写缓冲区,当客户端命令到来的时候,其会将执行后的写命令追加到AOF缓冲区和AOF重写缓冲区中。
当对AOF缓冲区进行重写时,新的命令仍然可以追加到AOF重写缓冲区中,重写完成后,会将AOF重写缓冲区中的内容写入到新的AOF文件中,然后对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧AOF文件的交替。
四、事件
redis服务器是一个事件驱动程序,服务器需要处理的事件可以分为文件事件和时间事件。
1、文件事件
是基于Reactor模式实现的网络通信程序,这里记录一下自己对Reactor模式的理解:
A、单线程IO模型,是最简单的形式,即一个while循环扫描是否有需要处理的IO事件,有的话进行阻塞式处理,其无法并发,处理效率低,且一个请求没处理完之前,剩余的只能被阻塞;
B、多线程IO模型,即每来一次连接,都新建一个线程处理,这种方式的缺点是资源消耗过多,并且线程的反复创建和销毁也需要开销,如果连接数过高,系统将会无法承载;
C、单线程Reactor模型,相当于维持一个事件的列表,每有一个新的连接过来,都会在这个列表上进行注册。其有一个reactor线程,不停的通过while循环扫描连接列表,当有IO事件发生时,其会调用对应的handler来处理。这样可以避免资源消耗过多,只有发生IO事件时才进行处理,而不是整个连接过程都在处理;
D、多线程Reactor模型,即将执行handler的操作维护在一个线程池中,这样即可通过非阻塞的方式来处理IO事件。
文件事件可以分为AE_READABLE(读)和AE_WRITABLE(写)两类事件。
2、时间事件
时间事件分为定时事件和周期性事件,其中定时事件只在指定的时间到达一次,周期性事件每隔一段时间到达一次
服务器在一般情况下只执行serverCorn函数这一个时间事件,其为周期性事件;
文件事件和时间事件是合作关系,服务器会轮流执行这两种事件,并且处理事件的过程也不会进行抢占;
由于在一个时间周期内,是先执行文件事件,再执行时间事件,所以很多情况下时间事件会比预期的时间要更晚一些。
五、客户端
1、客户端属性
客户端状态结构使用clients链表连接多个客户端状态,新添加的客户端状态会被放在链表的末尾;
客户端的flag属性使用不同的标示来表示客户端的角色,以及客户端当前所处的状态。
2、客户端状态
输入缓存区记录了客户端发送的命令请求,这个缓冲区大小不可以超过1GB;
缓冲区可以分为固定大小缓存区和可变大小缓冲区两种,其中固定大小为16KB,一般存储一些短回复("OK"等);可变大小缓冲区的最大大小不可以超过服务器设置的硬性标准;
如果输出缓冲区的大小超过了服务器设置的硬性限制,客户端会被立即关闭,此外,如果客户端在一定时间内,超过了服务器设置的软性标准,也会被关闭;
3、伪客户端
服务器会在以下两种情况下创建自己的伪客户端连接:
A、处理Lua脚本的伪客户端在服务器初始化时就会被创建,会一直存在直到服务器关闭;
B、载入AOF文件时使用的伪客户端会在载入工作开始时动态创建,载入工作完成后关闭。
六、服务端
1、命令请求过程
一个命令请求从发送到完成主要包括以下步骤:
a、客户端将命令请求发送给服务器;
b、服务器读取命令请求,并解析出命令参数;
c、命令执行器根据参数查找命令的实现函数,然后执行对应的函数并给出命令回复;
d、服务器将命令返回给客户端。
2、severCorn函数
默认每100ms执行一次,执行工作主要包括更新服务器状态信息,处理服务器接收的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操作等。
3、服务器启动过程
a、初始化服务器状态;
b、载入服务器配置;
c、初始化服务器数据结构;
d、还原数据库状态;
e、执行事件循环。