第二部分 单机数据库的实现
数据库
服务器中的数据库
- Redis服务器的全部数据库都保存在redisServer.db数组中,而数据库的数量使用redisServer.dbnum属性保存
切换数据库
- 客户端经过修改目标数据库指针,让它指向redisServer.db数组中的不一样元素来切换不一样的数据库
数据库键空间
- 数据库主要由dict和expires两个字典域构成,其中dict字典负责保存键值对,而expires字典则负责键的过时时间
- 由于数据库由字典构成,所以对数据库的操做都是创建在对字典操做之上
- 数据库的键老是一个字符串对象,而值则能够是任意一种Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象、有序集合对象。

设置键的过时时间java
- expires字典的键指向数据库中的某个键,而值则记录了数据库键的过时时间,过时时间以毫秒为单位的UNIX时间戳
过时键删除策略
-
三种不一样的删除策略redis
-
定时删除算法
- 在设置一个键的同时,建立一个定时器,让定时器在键过时时间来临时,当即执行对键的删除操做
- 优势: 对内存友好,能尽快地将过时键占用的内存释放
- 缺点: 对时间不友好,若是过时键不少,那么会占用大量CPU时间,影响服务器响应时间和吞吐量
-
惰性删除数据库
- 听任过时键无论,可是每次从键空间中获取键时,都检查取得的键是否过时,若是过时,就删除该键;若是没有过时,就返回该键
- 优势:对时间友好,只有当取出过时键时,才将该键删除
- 缺点:对空间不友好,大量过时无用键占用内存,由内存泄露的风险
-
按期删除数组
- 每一个一段时间,程序就对数据库进行一次检查,删除里面的过时键,至于要删除多少过时键,以及要检查多少个数据库,由算法决定
- 优势: 对上面两种策略的折衷。 对内存友好,对空间友好
- 关键是如何决定删除操做执行的时常和频率
-
Redis的过时键删除策略缓存
- Redis使用的是
按期删除
+ 惰性删除
保证过时键必定能被删除。并合理利用CPU时间和避免内存空间浪费
- 惰性删除 : 在执行命令以前,对输入的键进行过时检查
- 按期删除 : 在规定时间内,分屡次遍历服务器中多个数据库,从数据库中的expires字典随机检查一部分键的过时时间,并删除其中的过时键。
AOF、RDB和复制功能对过时键的处理
数据库通知
RDB持久化
前置知识: 进程和子进程

能够看出,子进程和父进程的代码区是共享的,而数据区和PCB块是父进程的副本。网络
子PCB中的PID字段为新分配子进程PID,数据集字段为数据集地址。数据结构
父进程和子进程是能够并行执行的。互不干扰。
RDB文件的建立与载入
- RDB持久化经过保存数据库中的键值对来记录数据库的状态 , 生成通过压缩的二进制文件。
-
建立过程
- SAVE命令由服务器进程直接执行保存操做,所以该命令会阻塞服务器
- BGSAVE由子进程执行保存操做,因此该命令不会阻塞服务器
-
载入过程
- 若是服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件还原数据库状态
- 若是AOF处于关闭状态,服务器才会使用RDB文件来还原数据库状态(前者丢失的数据更少)
自动间隔性保存
-
服务器状态中会保存全部用save选项设置的保存条件,当任意一个保存条件被知足时,服务器会自动执行BGSAVE命令。
#redis.conf
格式: save 时间 修改次数
save 900 1 (900s内修改1次)
save 300 10
save 60 10000 (60s内修改10000次)
struct redisServer{
struct saveparam *saveparams; //记录保存条件的数据
long long dirty; //修改计数器
time_t lastsave; //上一次执行保存的时间
}
RDB文件的结构
- 对于不一样类型的键值对,RDB文件会使用不一样的方式来保存他们

AOF持久化
AOF(Append Only File)持久化实现
- RDB持久化经过保存数据库中的键值对来记录数据库状态的不一样
- AOF持久化是经过保存Redis服务器所执行的写命令来记录数据库状态的

AOF文件的载入与数据还原
- 服务器只要载入并从新执行保存在AOF文件中的命令(使用伪客户端),就能够还原数据库原本的状态了。
AOF重写
- 为了解决AOF体积膨胀的问题,提供了AOF重写机制。AOF重写能够产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件保存的数据库状态是同样的,但体积更小
- AOF重写是一个由歧义的名字,程序无需对现有AOF文件进行任何装入、分析和写入操做。它是经过读取数据库中的键值对来实现的。
- AOF重写程序放在子进程中执行,此时服务器进程能够继续处理命令请求
- 子进程带有服务器进程数据的副本(数据一致性问题),那么若是在重写过程当中有新的写请求更改数据库状态,就会产生当前数据库状态与重写后的AOF文件状态不一致问题。
- 在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程建立新AOF文件期间,记录服务器执行的全部写命令。当子进程完成建立新AOF文件的工做后,服务器会将重写缓冲区中的全部内容追加到新的AOF文件的末尾,使得新旧两个AOF文件所保存的数据状态一致。随后,用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操做
-

-
在AOF重写期间,服务器的执行工做
- 执行客户端的命令
- 将执行后的写命令追加到AOF缓冲区(保证旧的AOF文件完整)
- 将执行后的写命令追加到AOF重写缓冲区(用于解决数据不一致问题)
事件
Redis服务器是一个
事件驱动程序
,服务器处理的事件分为文件事件和时间事件两类
文件事件
- 文件事件处理器是基于
Reactor模式
实现的网络通讯程序
- 文件事件处理器使用
IO多路复用
程序来同时监听多个套接字。并根据套接字目前执行的任务来为套接字关联不一样的事件处理器
- 当被监听的套接字准备好执行链接应答(accept)、读取(read)、写入(write)、关闭(close)时,与操做对应的文件事件就会产生,这时文件事件处理器就会调用套接字以前关联好的事件处理器来处理这些事件。
- 文件事件是对套接字操做的抽象,每次套接字变为可应答(acceptable)、可写(writeable)或者可读(reable)时,相应的文件事件就会产生
- 文件事件分为AE_READABLE事件(读事件)和AE_WRITEABLE事件(写事件)两类

-
一次完整的客户端与服务端链接事件示例
- Redis服务器运行时, 将链接应答处理器与 AE_READABLE事件关联起来
- 当Redis客户端发起链接时,那么监听套接字将产生AE_READABLE事件,触发链接应答处理器执行。处理跟客户端创建链接,并将客户端套接字的AE_READABLE事件与命令请求处理器关联起来
- 当客户端向redis发起请求的时候,那么客户端套接字将产生AE_READABLE事件,而后由对应的命令请求处理器来处理。读取客户端的命令内容,并传给相应程序执行。
- 那么当redis准备好给客户端响应数据以后,服务端会将AE_WRITEABLE事件跟命令回复处理器关联起来。当客户端准备尝试读取响应数据时,客户端套接字就会产生AE_WRITEABLE事件,触发命令回复处理器执行处理,将准备好的数据返回给客户端。 当回复写完时,服务器就会解除客户端套接字的AE_WRITABLE事件与命令回复处理器之间的关联。
时间事件
事件的调度与执行
客户端
- 服务器状态结构使用clients链表表示链接了多少个客户端状态,新添加的客户端状态会被放到链尾
- 客户端状态flags属性使用不一样标志来表示客户端的角色,以及客户端当前所在状态
- 输入缓冲区记录了客户端发送的命令请求,这个缓冲区大小不超过1GB
- 客户端使用argv , argc两个属性记录命令的参数和个数 , 而cmd属性记录了客户端要执行命令的实现函数

- 客户端有固定大小缓冲区和可变大小缓冲区两种, 其中固定大小缓冲区最大大小为16KB , 而可变大小缓冲区(由多个缓冲区组成, 用链表连接)最大大小不能超过服务器设置的硬性限制值
-
输出缓冲区限制值有两种,若是输出缓冲区的大小超过了服务器设置的硬性限制, 那么客户端会被当即关闭 ; 除此以外 ; 若是客户端在必定时间内,一直超过服务器设置的软性限制,那么客户端也会关闭.
#设置硬性 , 软性连接
命令名 客户端角色 硬性连接 软性连接 软性连接时长
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
- 客户端关闭的缘由 : 网络链接关闭 ; 发送了不合格时的命令请求 ; 成为CLIENT KILL目标 ; 空转时间超时 ; 输出缓冲区的大小超出限制.
服务端