Redis底层学习-数据库相关
Redis 底层学习-数据库
数据库结构
数据库结构,redisServer 中有一个 redisDb 对象数组,对应 redis 中的不同数据库,dbnum 记录了数据库在初始化的时候初始数据库个数。
上面是 redisDb 的数据结构,redisDb 中有 2 个字典,一个字典 dict 为真正保存数据的地方,键为 redis 数据的 key,值为具体的数据类型,通过指针指向不同数据库。expires 字典中存储了每个键对应的失效时间,每次在获取对应键的时候会先判断 expire 字典对应的失效时间是否小于当前时间,如果到期的话,就直接删除对应的键值对。
Redis 的过期删除策略
- 惰性删除策略:Redis 在所有读写操作之前都会调用 expireIfNeeded 对输入键进行检查,如果输入键已经过期,会将对应的输入键从服务器中删除。
- 定期删除策略:Redis 在周期性 servercron 函数执行时,activeExpireCycle 函数就会被调用。它会在规定的时间内,分多次遍历服务器中的多个数据库,从数据库中字典中随机检查一部分键的过期时间,并删除其中的过期键。
AOF、RDB 和复制过程对过期键的处理
生成 RDB 文件
在执行 save、bgsave 命令创建一个新的 RDB 文件时,程序会对数据库中的键检查,已过期的键不会保存到 RDB 文件中。
载入 RDB 文件
- 如果服务器是以主服务器模式运行,程序会对文件中保存的键进行检查。未过期的键会被载入到数据库,已过期的键会被忽略
- 如果服务器是以从服务器模式运行,程序会对所有键都导入,等待主服务器进行同步操作。
AOF 文件写入
当服务器以 AOF 持久化模式运行时,如果某个键已经过期,但是它没有被惰性删除或者定期删除,AOF 文件不会有任何影响。如果在被删除之后,程序会向 AOF 文件追加一条 DEL 命令,来显示删除已过期的键。
AOF 重写
和生成 RDB 文件类似,在执行 AOF 重写过程中,已过期的键不会被保存到重写后的 AOF 文件中。
复制
- 主服务器在删除一个过期键之后,会显示的向所有从服务器发送 DEL 命令,告诉从服务器删除过期键
- 从服务器在执行客户端发送的读命令,即使碰到过期键也不会过期删除,直接忽略
- 从服务器只有在收到主服务器发来的 DEL 命令之后,才会删除过期键
RDB 持久化
SAVE
save 命令会阻塞 redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
BGSVAE
BGSAVE 命令会派生出一个子进程,然后由子进程负责创建 RDB 文件,服务器进程继续处理命令请求。
- 在 bgsave 命令执行期间,客户端发送的 save 命令会被拒绝 ,同时执行会造成父进程和子进程产生竞争关系。
- 客户端发送的 bgsave 命令会被拒绝,两个 bgsave,两个子进程会产生竞争关系。
- 客户端执行 bgrewriteaof,两者不能同时进行,处于性能的考虑,两个子进程同时执行大量的磁盘操作,会影响性能。
自动间隔保存
我们可以通过配置 save 自动保存,来出发 bgsave 命令。
save 900 1
save 300 10
save 60 1000
redisServer 中含有 2 个属性,用来判断是否需要进行 save 操作
- dirty :每一次服务器在执行修改命令之后,累计+1,执行 save 或者 bgsave 之后,重置为 0。
- lastsave:记录了服务器从上一次执行 save 或者 bgsave 命令时的时间。
redis 的服务器周期性函数 serverCron 每隔 100 毫秒就会判断上述参数是否满足 save 对应的保存条件,如果满足那么就会触发 save 操作。
RDB 持久化文件是一个二进制文件,它记录了数据库各个库下面的所有键的类型和对应的值、过期时间等。
AOF 持久化
AOF 持久化是通过保存 redis 服务器所执行的写命令来记录数据库状态的。纯文本格式。
AOF 持久化的实现
redisServer 对象中含有一个 aof_buf 的缓冲区,当执行写命令的时候,会将对应的命令追加到 aof_buf 缓冲区中。
服务器在每一次结束一个事件循环之前,都会调用 flashAppendOnlyFile 函数,考虑是否将 aof_buf 缓冲区的内容写入和保存到 aof 文件中。flashAppendOnlyFile 函数的行为由服务器配置的 appendsync 的值来决定。
appendsync 的值选项
- always:将 aof_buf 的所有内容写入并同步到 aof 文件中。最安全,效率最低。即使出现故障,AOF 也只会丢失一个事件循环中产生的命令(还是可能会丢失,一个事件循环)
- everysec:将 aof_buf 的所有内容写入并同步到 aof 文件中,上一次同步 aof 文件时间大于 1s。足够快,最多丢失 1s 的数据
- no:将 aof_buf 的所有内容写入并同步到 aof 文件中,由操作系统决定何时进行同步。可能丢失上一次同步到现在的数据。
appendsync 默认的值选项为 everysec。
AOF 文件的重写
因为 aof 文件是通过保存被执行的写命令来记录数据状态的,所以随着服务器的运行,aof 文件的内容越来越多,需要进行重写来减少体积。
AOF 重写并不需要对 aof 文件进行任何读取、分析或者写入操作,这个功能的实现是通过读取服务器当前的数据库状态来实现的。
由于 aof 重写是通过子线程实现,这样可能保证服务器主线程可以继续执行请求。但是这样也会导致 aof 重写过程中,服务器新执行的命令对数据库的修改和 aof 文件中的状态不一致。
redis 服务器设置了一个重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 redis 服务器执行完一个写命令之后,它会同时将这个写命令发送到 aof 缓冲区和 aof 重写缓冲区。
当子进程执行完重写工作之后,它会向父进程发送一个信号,父进程在接受到该信号之后,会调用一个信号处理函数,并执行一下工作
- 将 aof 重写缓冲区的所有内容写入到新 aof 文件中,这时新 aof 文件所保存的数据库状态和服务器当前的数据库状态一致。
- 对新的 aof 文件进行改名,原子的覆盖现有的 aof 文件,完成新旧两个 aof 文件的替换。
事件
redis 服务器是一个事件驱动程序,服务器需要处理以下两类事件
- 文件事件:Redis 服务器通过套接字和客户端进行连接,而文件事件就是服务器对套接字操作的抽象。服务器和客户端的同学会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作
- 时间事件:Redis 服务器的一些操作(比如 serverCron 函数)需要对给定时间点执行,而时间事件就是服务器对这类定时操作的抽象。
文件事件
文件事件器的构成:套接字、IO 多路复用程序、文件事件分派器以及事件处理器
IO 多路复用程序负责监听多个套接字,并向文件事件分派器传送那些产生了时间的套接字。
IO 多路复用程序可能并发的监听到多个产生事件的套接字,IO 多路程序会把这些套接字按照顺序放入到一个队列里面,文件事件分派器有序的从队列中获取事件。
IO 多路复用程序的实现
Redis 的 IO 多路复用程序的所有功能都是通过包装常见的 select、epoll、evport 和 kqueue 这些 Io 多路复用函数库来实现的,每个 IO 多路复用函数库在 Redis 源码中都对应一个单独的文件。
因为 Redis 为每个 IO 多路复用函数库都实现了相同的 API,所以 io 多路复用程序的底层实现是可以互换的。程序会在编译的时候自动选择系统中性能最高的 IO 多路复用函数库来作为 Redis 的 IO 多路复用程序的底层实现。
事件的类型
- AE_READABLE : 当套接字变得可读时,或者有新的可应答套接字时,套接字产生 AR_READABLE 事件。
- AE_WRITEABLE:当套接字变得可写时,套接字产生 AE_WRITEABLE 事件
文件事件的处理器
- 连接应答处理器:对连接服务器的各个客户端进行应答
- 命令请求处理器:接受客户端传来的命令请求。
- 命令回复处理器:向客户端返回命令的执行结果。
- 复制处理器:当主服务器和从服务器进行复制操作时,主从服务器都需要关联复制处理器。
时间事件
Redis 的时间事件分为以下两类:
- 定时事件:让一段程序在指定的时间之后执行一次。
- 周期行事件:让一段程序每隔指定时间就执行一次。
一个时间事件主要由以下三个属性组成:
- id:服务器为时间事件创建的全局危矣 ID,ID 号按照从小打大的顺序递增。
- when:毫秒精度的 unix 时间戳,记录了时间事件的到达时间
- timeProc:时间事件处理器,一个函数,当时间事件到达时,服务器就会调用相应的处理器啦处理事件。
实现
服务器将所有时间事件都放在一个无序链表中,每当时间事件执行器运行时,它就遍历整个链表,查找所有已到达的时间事件,并调用相应的事件处理器。
事件的调度和执行
伪代码:
1 | def aeProcessEvents(): |
Redis 服务器主函数
1 | def main() |
运行流程图: