Reids笔记——单机数据库的实现

Part II 单机数据库的实现

9. 数据库

9.1 服务器中的数据库

  • Redis服务器默认会创建16个数据库。默认情况下,Redis客户端的目标数据库为0号数据库。

  • 客户端可以通过执行SELECT命令来切换目标数据库。

9.2 数据库键空间

  • RedisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(key space)

  • 因为数据库的键空间是一个字典,所以所有针对数据库的操作,实际上都是通过对键空间字典进行操作来实现的.

  • 清空整个数据库的FLUSHDB命令,用于返回数据库键数量的DBSIZE命令,类似的命令还有EXISTS、RENAME、KEYS等,这些命令都是通过对键空间进行操作来实现的。

  • 在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间

  • 如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行余下的其他操作

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

  • 通过EXPIRE命令或者PEXPIRE命令,可以以秒或者毫秒精度为某个键设置生存时间(Time To Live,TTL)

  • SETEX命令可以在设置一个字符串键的同时为键设置过期时间,服务器会自动删除生存时间为0的键

  • redisDb结构的expires字典保存了数据库中所有键的过期时间,称为过期字典

  • PERSIST命令可以移除一个键的过期时间,TTL命令以秒为单位返回键的剩余生存时间,而PTTL命令则以毫秒为单位

9.4 过期键删除策略

三种不同的删除策略:

  • 定时删除:

    • 在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作

    • 对内存友好,对CPU不友好。删除过期键这一行为可能会占用相当一部分CPU时间

  • 惰性删除:

    • 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。

    • 对CPU时间友好,对内存不友好。只要这个过期键不被访问,它所占用的内存就不会释放。

  • 定期删除:

    • 每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

    • 定期删除策略是前两种策略的一种整合和折中:每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。

9.5 Redis的过期键删除策略

Redis服务器实际使用的是惰性删除定期删除两种策略,通过配合使用这两种删除策略,可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。

惰性删除策略:
  • Redis命令在执行之前都会调用expirelfNeeded函数对输入键进行检查:

    • 如果输入键已经过期,那么expirelfNeeded函数将输入键从数据库中删除。

    • expirelfNeeded函数就像一个过滤器,它可以在命令真正执行之前,过滤掉过期的输入键,从而避免命令接触到过期键。

定期删除策略:
  • activeExpireCycle函数就会被周期性调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。

9.6 AOF、RDB和复制功能对过期键的处理

  • 在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时会进行检查,已过期的键不会被保存到新创建的RDB文件中。

  • AOF不会因为过期键而产生任何影响。当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加(append)一条DEL命令,来显式地记录该键已被删除。

  • 当主服务器删除一个过期键之后,它会向所有从服务器发送一条DEL命令,显式地删除过期键。从服务器即使发现过期键也不会自作主张地删除它,而是等待主节点发来DEL命令,保证主从服务器数据的一致性。

9.7 数据库通知

数据库通知是Redis 2.8版本新增加的功能,这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。

  • 当Redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知。

  • 关注“某个键执行了什么命令”的称为键空间通知,关注的是“某个命令被什么键执行了”是键事件通知

10. RDB持久化

  • RDB文件是一个经过压缩的二进制文件,用于保存和还原Redis服务器所有数据库中的所有键值对数据。

  • 该文件可以还原生成RDB文件时的数据库状态,即使Redis服务器进程退出,因为RDB文件是保存在硬盘里面的。

10.1 RDB文件的创建与载入

有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE

  • SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕

  • BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件

  • 因为AOF文件的更新频率通常比RDB高,如果开启了AOF,那么会优先使用AOF文件来还原数据库状态。

10.2 自动间隔性保存

默认只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行:

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

  • 服务器在300秒之内,对数据库进行了至少10次修改。

  • 服务器在60秒之内,对数据库进行了至少10000次修改。

服务器状态还维持着一个dirtyit数器,以及一个lastsave属性:

  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,对数据库状态进行了多少次修改。

  • lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。

10.3 RDB文件结构

图10-10展示了一个完整RDB文件所包含的各个部分。

一个RDB文件的databases部分可以保存任意多个非空数据库。每个非空数据库在RDB文件中都可以保存为SELECTDB、db_number、key_value_pairs三个部分,如图10-13所示。

SELECTDB常量的长度为1字节,当读入程序遇到这个值的时候,它知道接下来要读入的将是一个数据库号码。

RDB文件中的每个key_value_pairs部分都保存了一个或以上数量的键值对

  • 如果键值对带有过期时间的话,那么键值对的过期时间也会被保存在内。

  • 不带过期时间的键值对在RDB文件中由TYPE、key、value三部分组成。

  • 对于不同类型的键值对,RDB文件会使用不同的方式来保存它们。

Redis本身带有RDB文件检查工具redis-check-dump

11. AOF持久化

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

与RDB通过保存数据库中的键值对不同,AOF是通过保存Redis服务器所执行的写命令来记录数据库状态的,如图11-1所示。

11.1 AOF持久化的实现

AOF持久化功能的实现可以分为命令追加(append)文件写入文件同步(sync)三个步骤。

  • 当AOF打开时,在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾

  • 在服务器每次结束一个事件循环之前,都会考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面

  • 当appendfsync的值为always时,效率最慢但是最安全。为everysec时,足够快,最多丢失一秒钟的命令数据

11.2 AOF重写

因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。Redis会创建一个不带网络连接的伪客户端执行所有写命令。

为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写(rewrite)功能。

  • Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新AOF文件不会包含任何浪费空间的冗余命令

  • AOF文件重写并不需要对现有的AOF文件进行读取,而是读取服务器当前的数据库状态。

  • Redis将AOF重写程序放到子进程里执行,服务器进程(父进程)可以继续处理命令请求。使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

  • 为了避免父子进程数据不一致,Redis设置了一个AOF重写缓冲区,写命令会同时发送给AOF缓冲区和AOF重写缓冲区。

    • 当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾

    • 最后服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。

以上就是AOF后台重写,也即是BGREWRITEAOF命令的实现原理。

12. 事件

Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:

  • 文件事件(file event):Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。

  • 时间事件 (time event):Redis服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象。

12.1 文件事件

Redis基于Reactor模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(fle event handler):

  • 文件事件处理器使用I/O多路复用 (multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。保持了Redis内部单线程设计的简单性

  • 文件事件处理器的四个组成部分分别是套接字、I/O多路复用程序、文件事件分派器(dispatcher),以及事件处理器。

  • 文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答 (accept)、写入、读取、关闭等操作时,就会产生一个文件事件。因为一个服务器通常会连接多个套接字,所以多个文件事件有可能会并发地出现。

  • 尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都放到一个队列里面,然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。

  • 在事件处理器里面,服务器最常用的要数与客户端进行通信的连接应答处理器命令请求处理器命令回复处理器

12.2 时间事件

Redis的时间事件分为以下两类:

  • 定时事件:让一段程序在指定的时间之后执行一次。比如让程序X在当前时间的30毫米之后执行一次。

  • 周期性事件:让一段程序每隔指定时间就执行一次。比如让程序Y每隔30毫秒就执行一次。

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

  • 持续运行的Redis服务器需要定期对自身的资源和状态进行检查和调整,从而确保服务器可以长期、稳定地运行,这些定期操作由serverCron函数负责执行。

  • Redis服务器以周期性事件的方式来运行serverCron函数,在服务器运行期间,每隔一段时间,serverCron就会执行一次,直到服务器关闭为止。在Redis2.6版本,服务器默认规定serverCron每秒运行10次,平均每间隔100毫秒运行一次。

12.3 事件的调度与执行

因为服务器中同时存在文件事件和时间事件两种事件类型,所以服务器必须对这两种事件进行调度。

对文件事件和时间事件的处理都是同步、有序、原子地执行的,服务器不会中途中断事件处理,也不会对事件进行抢占。

13. 客户端

Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接。

  • 通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。

  • 对于每个与服务器进行连接的客户端,服务器都为这些客户端建立了相应的redisClient结构(客户端状态)

  • Redis服务器状态结构的clients属性是一个链表,这个链表保存了所有与服务器连接的客户端的状态结构

13.1 客户端属性

  • 客户端状态的fd属性记录了客户端正在使用的套接字描述符。

  • 执行命令所得的命令回复会被保存在客户端状态的输出缓冲区里面,每个客户端都有两个输出缓冲区可用,一个缓冲区的大小是固定的,另一个缓冲区的大小是可变的:

    • 固定大小的缓冲区用于保存那些长度比较小的回复,比如OK、简短的字符串值、整数值、错误回复等等。

    • 可变大小的缓冲区用于保存那些长度比较大的回复,比如一个非常长的字符串值,一个由很多项组成的列表等。

13.2 客户端的创建与关闭

  • 如果客户端是通过网络连接与服务器进行连接的普通客户端,服务器就会调用连接事件处理器,将这个新的客户端状态添加到服务器状态结构clients链表的末尾。

  • 网络连接关闭、发送了不合协议格式的命令请求、成为CLIENT KILL命令的目标、空转时间超时、输出缓冲区的大小超出限制,以上这些原因都会造成客户端被关闭。

  • 处理Lua脚本的伪客户端在服务器初始化时创建,这个客户端会一直存在,直到服务器关闭。

  • 载入AOF文件时使用的伪客户端在载入工作开始时动态创建,载入工作完毕之后关闭。

14. 服务器

  • 一个命令请求从发送到完成主要包括以下步骤:

    • 客户端将命令请求发送给服务器;

    • 服务器读取命令请求,并分析出命令参数;

    • 命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;

    • 服务器将命令回复返回给客户端。

  • serverCron函数默认每隔100毫秒执行一次,它的工作主要包括更新服务器状态信息,处理服务器接收的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操作等等。

  • 服务器从启动到能够处理客户端的命令请求需要执行以下步骤:

    • 1)初始化服务器状态;2)载入服务器配置;3)初始化服务器数据结构;4)还原数据库状态;5)执行事件循环。

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值