Redis单机数据库实现

Redis笔记


单机数据库的实现

服务器中的数据库

Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,sb数组的每个项都是一个redis.h/redisDb结构,每个redisDb结构代表一个数据库:

struct redisSerer{

    //一个数组,保存着服务器中的所有数据库
    redisDb *db;

    //服务器的数据库数量 默认为16
    int dbnum;

};

默认情况下,Redis客户端的目标数据库为0号数据库,但客户端可以通过执行SELECT来切换目标数据库。实现原理修改在redisClient中的db指针。

数据库键空间

Redis是一个键值对数据库服务器,服务器中的每个数据库都由一个redis.h/redisDb结构表示。redisDb结构中的dict字典保存了数据库中的所有键值对,这个字典称为键空间 key space。

键空间操作包括添加,删除,更新,取值操作

设置键的生存时间或过期时间

通过EXPIRE命令或者PEXPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间 time to live,TTL。在指定的秒数或者毫秒数之后,服务器会自动删除生存时间为0的键

设置过期时间
  • EXPIRE <key> <ttl> key的生存时间设置为ttl秒
  • PEXPIRE <key> <ttl> key的生存时间设置为ttl毫秒
  • EXPIREAT <key> <timestamp> key的生存时间设置为timestamp的秒数时间戳
  • PEXPIREAT <key> <timestamp> key的生存时间设置为timestamp的秒数时间戳
过期键删除策略
  • 定时删除:创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
  • 惰性删除:每次从键空间中获取键时,都检查取得的键是否过期,如果过期就该删除键;如果没有就返回键
  • 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。
Redis的过期键删除策略

Redis服务器实际使用的是惰性删除和定期删除两种策略:是在CPU时间和内存空间之间的平衡。

惰性删除策略的实现

过期键的惰性删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查
- 如果输入键过期,则从数据库中删除
- 如果输入键未过期,不做动作

定期删除策略的实现

定期删除策略由redis.c/activeExpireCycle函数实现,每当Redis服务器周期性操作redis.c/serverCron函数执行时,activeExpireCycle函数就会被调用,多次遍历服务器中的各个数据库,从数据库expires字典中随机检查一部分键的过期时间,并删除其中的过期键
- 每次从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键
- 全局变量current_db会记录当前activeExpireCycle函数检查的进度,保存进度以备下一次进行处理。
- 随着函数不断执行,服务器中的所有数据都会被检查一遍。之后将current_db置为0,然后在开始新一轮的检查。

AOF、RDB和复制功能对过期键的处理
生成RDB文件

执行SAVE或者BGSAVE明亮创建一个新的RDB文件,对数据库中的过期键进行过滤。

载入RDB文件

启动Redis服务器时,如果服务器开启了RDB功能,那么服务器就会载入RDB文件:

  • 服务器以主服务器模式运行,会对文件中保存的键进行检查,未过期的键被载入进数据库,过期键会被忽略。
  • 服务器以从服务器模式运行,载入文件中所有的键。当主服务器进行数据同步时,从服务器的数据库会被清空
AOF文件写入

当服务器以AOF持久化模式运行,如果数据库中的某个键已经过期,但还没被惰性删除或者定期删除。AOF文件不会因为过期键而产生影响。在删除之后,会向AOF文件追加一条DEL命令,来显示的记录该键已被删除。

复制

当服务器运行在复制模式下,从服务器的过期键删除由主服务器控制
- 主服务器在删除过期键后,显式的向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键
- 从服务器在结构主服务器发来的DEL命令后,才会删除过期键

数据库通知
重点回顾
  • Redis服务器的所有数据库保存在redisServer.db数组中,数据库的数量则由redisServer.dbnum属性保存
  • 客户端通过修改目标数据库指针,让它指向redisServer.db数组中的不同元素来切换不同的数据库。
  • 数据库主要由dict和expires两个字典构成,dict字典保存键值对,expires字典保存键的过期时间
  • 数据库由字典构成,对数据库的操作都是基于字典
  • expires字典的键指向数据库中的某个键,而值记录了数据库键的过期时间,过期时间是一个以毫秒为单位的UNIX时间戳
  • Redis使用惰性删除和定期删除两种策略来删除过期的键
  • 当一个过期键被删除之后,服务器会追加一条DEL命令到现有AOF文件的末尾,显式的删除过期键
  • Redis命令对数据库进行修改后,服务器会根据配置向客户端发送数据库通知

RDB持久化

Redis是内存数据库,将自己的数据库状态存在内存里面,所以需要持久化

RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原RDB文件。

RDB文件的创建与载入

SAVE和BGSAVE可以用于生成RDB文件。
- SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕
- BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程 父进程 继续处理命令请求
- 载入RDB文件是在服务器启动时自动执行的

另外:
- AOF文件更新频率比RDB文件高,所以优先使用AOF文件来还原数据库状态
- 只有在AOF持久化功能关闭时,服务器才会使用RDB文件来还原数据库状态

SAVE命令执行时,Redis服务器会被阻塞,客户端发送的所有指令都会被拒绝

BGSAVE命令执行期间,客户端发送的SAVE会被服务器拒绝,服务器禁止SAVE和BGSAVE,避免父进程和子进程同时调用,防止产生竞争

BGSAVE命令执行期间,客户端发送的BGSAVE会被服务器拒绝,同时执行两个BGSAVE也会产生竞争条件

BGREWRITEAOF和BGSAVE两个命令都由子进程执行,但同时执行大量的磁盘写入操作,是性能低效的

自动间隔性保存

服务器允许用户通过选项设置多个保存条件save项,让服务器每隔一段时间自动执行一次BGSAVE命令

save 900 1
服务器在900秒内,对数据库进行了至少一次修改

dirty计数器和lastsave属性

saveparams数组外,服务器还维持一个dirty计数器,以及一个lastsave属性
- dirty记录了上一个成功执行save或者bgsave命令之后,服务器对数据库进行了多少次修改
- lastsave属性是一个unix时间戳

RDB文件结构
REDISdb_versiondatabasesEOFcheck_sum
5个字节,文件开头部分,校验是否为RDB文件4个字节,文件版本号包含任意多个数据库,以及键值对数据1字节,正文内容结束标记check_sum是一个8字节长,保存着校验和
SELECTDBdb_numberkey_value_paris
长度为1字节,接下来要读入的将是一个数据库号码保存数据库号码保存数据库中所有键值对的大小

AOF持久化

Redis还提供AOF(Append Only File)持久化功能

AOF持久化的实现

AOF持久化实现分为命令追加、文件写入、文件同步三个步骤。

AOF文件的写入与同步

Redis服务器进程是一个事件循环,文件事件负责接收客户端的额命令请求,以及向客户端发送命令回复,而时间事件则负责执行向serverCron函数这样需要定时运行的函数

AOF文件的载入与数据还原

AOF文件里面包含了重建数据库状态的所有写命令,服务器只要读入并执行AOF里面保存的写命令,就可以还原服务器关闭之前的数据库状态。

AOF文件重写

AOF文件随着内容增加,需要重写,新建一个新AOF文件去除老AOF文件的冗余

AOF文件后台重写

aof_rewrite将阻塞redis服务器进程,所以将重写放入子进程里执行
- 子进程进行AOF重写,服务器父进程可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以避免使用锁,也能保证数据安全

  • AOF缓冲区的内容会被定期写入和同步到AOF文件,对现有AOF文件的处理会如常进行
  • 从创建子进程开始,服务器执行的所有写命令都被记录到AOF重写缓冲区里面。
  • 当子进程完成AOF重写工作之后,会向父进程发送一个信号,父进程在街道该信号之后,会调用一个信号处理函数
  • 将AOF重写缓存区的所有内容写入新AOF文件中,
  • 对新的AOF文件进行改名,原子的覆盖现有的AOF文件,完成新旧两个AOF文件的替换

事件

文件事件:Redis服务器通过套接字与客户端进行连接,文件事件就是服务器对套接字操作的抽象。

时间事件:Redis服务器中的一些操作需要在给定时间点执行,时间事件就是服务器对这类定时操作的抽象。

文件事件

文件事件处理器:

  • 文件事件处理器使用IO多路复用程序来监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器
  • 当被监听的套接字准备好执行连接应答,读取,写入,关闭等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联号的事件处理器来处理这些事件
文件处理器构成
套接字IO多路复用程序文件事件分派器事件处理器
当套接字准备好执行连接应答,写入,读取,关闭等操作时就会产生一个文件事件箭筒多个套接字,产生事件的套接字放入队列接收套接字,根据套接字产生的事件类型,调用相应的事件处理器定义服务器应该执行的动作
文件事件的处理器
  • 连接应答处理器
  • 命令请求处理器
  • 命令回复处理器
时间事件

服务器将时间事件放在一个无序链表中,每当时间事件执行器运行时,就遍历整个链表,查找所有已到达的事件事件,并调用相应的事件处理器

serverCron函数:

  • 更新服务器统计信息
  • 清理数据库中的过期键值对
  • 关闭和清理连接失效的客户端
  • 尝试进行AOF或RDB持久化操作
  • 服务器是主服务器,对从服务器进行定期同步
  • 处于集群模式,对集群进行定期同步和连接测试

客户端

Redis服务器是典型的一对多服务器程序:
服务器将客户端存在clients链表中

伪客户的fd属性值为-1,命令请求来源于AOF文件或者Lua脚本

命令与命令参数
typedef struct redisClient{

    robj **argv;//每行是数组,argv【0】是执行命令,之后是命令参数

    int argc;// 记录argv数组的长度

}redisClient;
重点回顾
  • 输入缓冲区记录了客户端发送的命令请求,这个缓冲区的大小不能超过1GB
  • 客户端有固定大小缓冲区和可变大小缓冲区两种缓冲区可用,其中固定大小缓冲区的最大为16KB,可变大小缓冲区不能超过服务器设置的硬性限制值。
  • 出书缓冲区限制有两种,如果输出缓冲区的大小超过了服务器设置的硬性限制,那么客户端会被立即关闭。如果客户端在一定时间内,一直超过服务器设置的软性限制,那么客户端也会被关闭。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值