引言
单机数据库的实现
数据库
数据库的实现
数据库服务端和客户端实现的数据结构:
typedef struct redisServer {
int dbnum; // 服务器的数据库数量,默认值是16
redisDb[] *db; // 一个数组,保存着服务器中的所有数据库,数组大小由dbnum决定
......
list *clients; // 连接到此服务器的客户端链表
} redisServer;
typedef struct redisClient {
redisDb *db; // 记录客户端当前正在使用的数据库
dict *dict; // 键空间,字典类型,保存着数据库中的所有键值对
dict *expires; // 过期时间,字典类型,键是指向对象的指针,值是long类型的时间戳
......
} redisClient;
数据库客户端和服务端的关系如图所示:
上节图示中连接的是1号数据库,切换数据库指令:SELECT <dbnum>
,比如 SELECT 2
,就能够切换到2号数据库。
数据库键空间
- 键空间的键也就是数据库的键,每个键都是一个字符串对象
- 键空间的值也就是数据库的值,每个值都可以是前面所述五种 对象 的其中一种
Redis的过期键删除策略
对于过期键的删除一般有如下三种方式:
- 定时删除 在设置键的过期时间的同时,创建一个定时器(timer),定时结束立即删除
- 惰性删除 每次响应请求从键空间获取键的时候检查是否过期,如果过期进行删除
- 定期删除 每隔一段时间,对数据库进行检查,批量删除过期的键,是上述两种方法的整合和折中
Redis是配合使用惰性删除和定期删除两种策略,以很好得在合理使用CPU时间和避免浪费内存空间之间取得平衡。
AOF、RDB 和 复制功能 对过期键的处理
当服务器运行在复制模式(主从模式)下时,从服务器的过期键删除动作由主服务器控制。 也就是说主服务器检测到一个键过期时候会进行删除,同时发送给所有从服务器一条删除指令,进行从服务器过期键的删除。 我们知道,主服务收到请求的时候,会先检查键是否过期,如果不过期返回value,如果过期,返回nil。 但从服务器收到请求的时候不会进行检查,只要没有收到主服务器发送过来的删除指令进行删除,即便过期也会返回value。
数据库通知
- 键空间通知(key-space notification):某个键执行了什么命令
- 键事件通知(key-event notification):某个命令被什么键执行了
RDB持久化
RDB文件的创建与载入
Redis所有数据都保存在内存中,那么一旦服务器进程退出,Redis数据就消失。 为了避免数据意外丢失,Redis提供了RDB持久化功能。 RDB持久化生成的RDB文件是一个经过压缩的二进制文件,通过RDB文件可以还原到 生成文件时候的数据库状态。
持久化命令有两个:
- SAVE:阻塞式持久化,持久化过程中不能使用Redis其他指令
- BGSAVE:非阻塞式持久化,会新开一个子线程来进行持久化,不影响Redis其他指令
只要有RDB文件,启动Redis的时候就会自动载入,不需要手动调用指令。
自动间隔性保存
默认配置:
save 900 1
save 300 10
save 60 10000
只要满足900秒修改了一次,300秒修改了10次 或 60秒修改了1000次,那就自动执行一次 BGSAVE 操作。
AOF持久化
RDB持久化的是数据库状态,也就是将数据库中所有有效数据保存到RDB文件; 而AOF持久化的是操作命令,也就是将对数据新增、修改的指令保存到AOF文件。
- 如果开启了AOF持久化功能,那么服务器会优先使用AOF文件还原数据库状态
- 只有AOF持久化出于关闭状态时,服务器才会使用RDB文件来还原数据库状态
AOF持久化的实现
- 命令追加(append)
appendfsync有三个配置选项:always、everysec、no - 文件写入
- 文件同步(sync)
AOF文件的载入与数据还原
读取AOF文件,将所有命令重新执行一遍
AOF重写
如果一条list数据有新增,有修改删除,那么很多条指令之后可能只包含了很少的有效数据, 这时候可以从数据库读取当前有效的数据,用一条新增指令替换掉之前的很多条指令。
事件
Redis是一个事件驱动服务器,服务器需要处理以下两类事件:
- 文件事件(file event)
- 时间时间(time event)
文件事件
Redis服务器通过套接字与客户端连接,而文件事件就是套接字对文件事件的抽象。 服务器与客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
时间事件
时间事件分为如下两类:
- 定时事件
- 周期性事件
客户端
客户端属性
- 通用属性
- 特定功能相关属性
服务器
命令请求的执行过程
serverCron函数
Redis服务器中的serverCron函数每隔100毫秒执行一次,这个函数负责管理服务器的资源, 并保持服务器自身的良好运转。
- 更新服务器时间缓存
- 更新LRU时钟
- 更新服务器每秒执行命令次数
- 更新服务器内存峰值记录
- 处理SIGTERM信号
- 管理客户端资源
- 管理数据库资源
- 执行被延迟的BGREWRITEAOF
- 检查持久化操作的运行状态
- 将AOF缓冲区的内容写入到AOF文件
- 关闭异步客户端
- 更新cronloops计数器的值
多机数据库的实现
复制
旧版功能的实现
- 同步(sync)
使用SLAVEOF
指令,将从服务器的状态更新至主服务器当前的数据库状态 - 命令传播(command propagate)
主服务器数据被修改时,主服务器将指令发送给从服务器,将主从服务器状态再次同步为一致状态
旧版功能缺陷
- 初次复制:使用同步指令
sync
生成RDB文件进行同步,一般来说这个没问题 - 断线后重复制:断线后重连复制的是全部RDB文件,这里是不需要的,新版主要解决这个问题
新版复制功能的实现
- 完整重同步(full resynchronization):用于处理初次复制的情况,相当于
SALVEOF
指令 - 部分重同步(partial resynchronization):用于处理断线后重复值的情况
部分重同步的实现
- 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量
- 主服务器的复制挤压缓冲区(replication backlog):存储断线时间段内的指令,默认1M内存空间
- 服务器的运行ID
PSYNC命令的实现
Sentinel
Sentinel(哨岗、哨兵)是Redis高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance) 组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器 进入下线状态时,自动将下线主服务器属下的某个服务器升级为新的主服务器,然后由新的主服务器代替已下线的从服务器继续 处理命令请求。当已下线的主服务器重新上线之后,降级为从服务器继续运行。
集群
Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharing)来进行数据共享,并提供复制和故障转移功能。
节点
独立功能的实现
发布与订阅
事务
Lua脚本
排序
二进制位数组
慢查询日志
监视器
整理
- 集合键
参考资料
文档信息
- 本文作者:Bob.Zhu
- 本文链接:https://adolphor.github.io/2016/08/20/Redis-design-and-implementation/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)