Redis单机数据库的实现

数据库

  1. 客户端状态redisClient结构的db属性记录了客户端当前的目标数据库,这个属性指向redisDb结构的指针
  2. redisClient.db指针指向redisSever.db数组的其中一个元素,这个元素就是数据库的目标数据库
  3. redisDb结构的dict字典保存了数据库中的所有键值对
  4. 读取一个键后,服务器会根据键是否存在更新服务器中的键空间命中次数或键空间不命中次数,这两个属性在INFO state命令在keyspace_hits和keyspace_misses属性中查看
  5. 键的LRU用于计算键的闲置时间
  6. 通过EXPIRE命令或者PEXPIRE命令,设置键的生存时间
  7. 通过EXPIREAT或PEXPIREAT设置键的过期时间
  8. TTL和PTTL命令接受一个带有生存周期或过期时间的键,返回这个键剩余的
  9. redisDb结构的expires字典保存了数据库中所有键的过期时间,称这个字典为过期字典
  10. PERSIST命令是PEXPIREAT命令的反操作,在过期字典中查找给定的键和值在过期字典中的关联,达到移除过期时间的目的
  11. 过期键的判定:检查给定的键是否存在于过期字典,取得过期时间,如果unix时间戳大于键的过期时间,则键已过期
  12. 过期键删除策略:
  • 定时删除:设置键的同时创建一个定时器,在过期时间来临时立即删除
  • 惰性删除:每次从键空间获取键时,检查是否过期,过期则删除,未过期则返回该键
    1. 惰性删除策略由db.c/expireIfNeeded函数实现
  1. 定期删除策略由redis.c/activeExpireCycle函数实现

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

  1. 以主服务器模式运行时,载入RDB文件时,过期键不会被载入数据库
  2. 以从服务器模式运行时,载入RDB文件,无论是否过期都会被载入数据库
  3. AOF文件写入,以AOF持久化模式运行时,如果数据库中的某个键已过期但还没有被惰性删除或定期删除,AOF文件不会因为过期键产生影响
  4. 过期键被删除后,程序会向AOF文件追加一条命令,显示过期键已经被删除
  5. AOF重写时,不会重写已删除的键
  6. 主从复制模式下,从服务器的过期键操作由主服务器控制,保持一致性

数据库通知

  1. 服务器配置的notify-keyspace-events选项决定了服务器锁发送通知的类型
  • 键空间通知和键事件通知AKE
  • 键空间通知AK
  • 键事件通知AE
  • 只发送和字符串键有关的键空间通知K$
  • 只发送和列表键有关的键事件通知El
  1. 发送通知的功能是由notify.c/notifyKeyspaceEvent函数实现

    1
    2
    3
    4
    void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid); 
    //type是当前想要发送的通知类型,剩余三个参数分别是事件的名称、产生事件的键、产生事件的数据库好
    //如果给定的通知类型不是服务器允许的函数立刻返回
    //给定的服务器允许发送通知,检测具体通知类型,构建发送通知
  2. SADD命令由saddCommand函数实现

  3. DEL命令由delCommand函数实现
  4. PUBLISH命令是由publishPublishMessage函数实现的

RDB持久化

RDB持久化

  1. save命令用于生成rdb文件,save命令会阻塞redis服务器进程,直到rdb文件创建完毕为止
  2. bgsave命令会派生出一个子进程,由子进程负责创建rdb文件,服务器进程继续处理命令请求
  3. rbd文件载入是由服务器启动时自动执行的,只要检测到rdb文件存在,就会自动载入rdb文件
  4. 如果服务器开启了AOF持久化功能,服务器会优先使用AOF文件来还原数据库,AOF文件的更新频率比RDB文件的更新频率高
  5. 在bgsave命令执行期间发送命令的一些情况
  • 在bgsave命令处理期间,使用save和bgsave命令会被拒绝
  • 在bgsave命令执行期间,使用bgrewirteaof命令会被延迟到bgsave命令完成之后
  • 在bgrewriteaof命令执行期间,客户端发送bgsave命令会被拒绝
  1. rdb文件载入期间,服务器一直处于阻塞状态,直到载入工作完成为止
  2. 配置服务器的save选项,使得服务器每隔一段时间自动执行一次bgsave命令,如果用户没有配置会选择默认的配置条件。saveparams数据保存着所有save选项设置的条件,只要任一条件被满足就会执行bgsave命令
  3. dirty计数器记录上一次成功执行save命令或bgsave命令后服务器对数据库的状态,进行了多少次修改
  4. lastsave属性是unix时间戳,记录了服务器上一次save或bgsave的时间
  5. redis文件结构:
  • 最开头是REDIS,通过这五个字符,程序在载入文件时快速检查载入的文件是否是rdb文件
  • db_version长度是4字节,值是一个字符串表示的整数,这个整数记录了rdb文件的版本号
  • databases部分包含0个或任意个数据库,各个数据库的键值对数据:若数据库状态为空,则这个部分也为空
  • EOF常量为1字节标志着redis正文结束
  • check_num是一个8字节长的无符号整数,保存一个校验和,由前面四个部分计算得出
  • 服务器载入rdb文件时会载入数据计算的校验和与check_num所记录的校验和进行对比
  1. 每个非空数据库可以保存为三个部分:
  • SELECTDB常量长度为1字节
  • db_number保存数据库号码,根据号码不同这部分长度为1、2、5字节不等,根据这个号码进行数据库切换
  • key_value_pairs部分保存了数据库的所有键值对,如果键值对带有国企时间,那么过期时间也和键值对保存在一起,根据键值对的数量、类型、内容以及是否过期等条件不同,key_value_pairs部分长度也有不同
  1. key_value_pairs:不带过期时间的键值对由type/key/value三个部分组成
  • TYPE记录了value的类型,长度为1字节
  • key总是一个字符串对象
  • 带有时间过期时间的字符串键值对带有:EXPIRETIME_MS代表读入的是一个以毫秒为单位的过期时间,ms是一个8个字节长的带符号整数,记录过期时间

rdb文件

  1. 没有包含任何数据库数据时的rdb文件由以下四个部分组成
  • 五个字节的”REDIS”字符串
  • 四个字节的版本号
  • 一个字节的EOF
  • 八个字节的校验和
  1. 包含数据库的rdb文件,数据库将由以下三个部分组成
  • 一个一字节长的特殊值SELECTED
  • 一个长度为1、2、5字节的数据库号码
  • 任意个键值对
  1. redis带有rdb文件检查工具redis-check-dump

AOF持久化

AOF持久化

  1. AOF持久化保持数据库的状态是将数据库执行的命令保存到AOF文件中
  2. 命令追加:服务器执行完一个命令后,以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区结尾
  3. 文件写入:将aof_buf缓冲区中的内存写入和保存到AOF文件中
  4. 文件同步:对AOF文件进行同步
  5. 服务器配置anpendfsync的值决定AOF持久化功能的效率和安全性
  • always:每个事件循环都要将缓冲区的所有内容写入并同步到AOF文件
  • everysec:将缓冲区的所有内容写入到AOF文件,如果上次同步的时间超过1s,则再次对AOF文件进行同步
  • no:将aof_buf缓冲区的所有内容写入到AOF文件,但是不对AOF文件进行同步,何时同步由操作系统来决定
  1. AOF还原数据库的状态,重新执行一遍AOF文件里面保存的命令

AOF重写

  1. 为解决AOF文件体积膨胀的问题,Redis服务器可以创建一个新的AOF文件看来替代旧的AOF文件,新旧两个文件所保存的数据库状态相同
  2. 通过减少命令的数量来实现来减少AOF文件的体积
  3. AOF重写程序放到子进程中进行
  4. 为解决重写期间产生数据不一致问题,Redis服务器设置了一个AOF重写缓冲区
  5. AOF缓冲区的内容会定期被写入和同步到AOF文件,对现有的AOF文件的处理工作会如常进行
  6. 创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区
  7. 完成AOF重写工作后
  • 子进程完成重写工作后,将AOF缓冲区的所有内容写入到新的AOF文件中
  • 将新的AOF文件进行改名,原子地覆盖现有的AOF文件

事件:Redis服务器是一个事件驱动程序

文件事件

  1. 文件事件处理器使用I/O多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器
  2. 被监听的套接字准备好执行,连接应答、读取、写入、关闭等操作,与操作对应的文件事件就会产生
  3. API
  • ae.c/aeCreateFileEvent函数接受一个套接字描述符、一个事件类型、一个事件处理器作为参数,将给定套接字的给定事件加入I/O多路复用程序的监听范围内,并对事件和事件处理器进行关联
  • ae.c/acDeleteFileEvent函数接受一个套接字描述符和一个监听事件类型作为参数,让I/O多路复用程序取消给定套接字的给定事件的监听,取消事件和事件处理器之间的关联
  • ae.c.aeGetFileEvent函数接受一个函数描述符,返回套接字正在被监听的事件类型,没有被监听AE_NONE,读时间被监听AE_READABEL,写时间正在被监听AE_WRITEABLE,读事件和写事件正在被监听AE_READABEL|AE_WRITEABLE
  • ae.c/aeWait函数接受一个套接字描述符、一个事件类型和一个毫秒数为参数,在给定时间内阻塞并等待套接字的给定类型事件产生,当事件成功产生或等待超时之后函数返回
  • ae.c/aeApiPoll函数接受一个sys/time.h/struct timeval结构为参数,并在指定的时间内阻塞并等待所有被aeCreateFileEvent函数设置为监听状态的套接字产生文件事件,当至少有一个事件产生或等待超时后函数返回
  • ae.c/aeGetApiName函数返回I/O多路复用程序底层所使用的I/O多路复用库的名称:返回”epoll”表示底层为epoll函数库,返回”select”表示底层为select
  1. 文件事件的处理器
  • 连接应答处理器
  • 命令请求处理器
  • 命令回复处理器

时间事件

  1. 定时事件、周期性事件
  2. 一个时间事件主要由以下三个属性组成
  • id
  • when:毫秒进度的unix时间戳,记录事件的到达时间
  • timeproc:时间事件处理器,时间事件到达时,服务器就会调用相应的处理器来处理事件
  • 如果事件处理器返回ae.h/AE_NOMORE,那么这个事件为定时事件
  • 如果返回一个非AE_NOMORE的整数值,那么是周期性事件
  1. 服务器将所有的时间事件都放在一个无序链表中,当时间事件执行器运行时,遍历整个链表查找所有的已到达时间事件,调用相应的事件处理器
  2. 时间事件应用实例:serverCron函数

事件的调度与执行

  1. 由ae.c/aeProcessEvents函数负责
  2. 调度和执行规则
  • aeApiPoll函数的最大阻塞时间由到达时间最接近当前时间的时间事件决定,这个方法可以避免服务器对时间事件进行轮询,也可以确保不会阻塞太长时间
  • 文件事件随机出现,等待并处理完文件事件后,没有时间事件到达则继续等待并处理文件事件,当时间到达时间事件后,处理时间事件
  • 文件事件和时间事件的处理是同步、有序、原子地执行
  • 时间事件会比预设的到达时间稍晚一点

客户端

  1. 根据客户端类型,fd属性的值可以是-1或者大于-1的整数
  • 伪客户端的fd属性是-1,伪客户端:AOF文件还原数据库状态,执行Lua脚本中包含的Redis命令
  • 普通客户端的fd属性的值大于-1
  1. 默认情况下客户端没有名字,可以通过setname来设置客户端名称
  2. 客户端标志flags记录了客户端的角色
  3. 输入缓冲区根据输入内容动态的增大或者缩小,但是它的大小不会超过1GB
  4. 输出缓冲区:每个客户端有两个输出缓冲区,一个是固定大小的,一个是可变的
  • 固定大小的缓冲区可以用来保存长度比较小的回复,最大为16KB
  • 可变大小的缓冲区可以用来保存长度比较大的回复,最大不能超过硬性限制值
  1. 客户端的状态authenticated属性用来记录客户端是否通过了身份验证
  • 如果authenticated的值为0,表示客户端未通过身份验证
  • 如果是0则表示通过了身份验证
  1. 时间:
  • ctime属性记录了创建客户端的时间
  • lastinteration属性记录了客户端与服务器最后一次互动的时间
  • lastinteration可以用来计算客户端的空转时间
  • obuf_soft_limit_reached_time属性记录了输出缓冲区第一次到达软性限制的时间

服务器

  1. 若cmd的指针指向null,说明用户输入的命令名字找不到相应的命令实现,服务器不再执行后续操作,并向客户端返回一个错误
  2. 更新时间缓存:
  • 服务器在只会打印日志、更新服务器的LRU时钟、决定是否执行持久化、计算服务器上限时间这类时间精度要求不高的功能上,使用unixtime属性和mstime属性
  • 对于键设置过期时间,添加慢查询日志这种需要高精度时间的功能来说会再次执行系统调用,获取最准确的系统当时时间
  1. severCron函数会以每10秒一次的频率更新lrulocck的值
  2. severCron函数中的trackOperationsPerSecond函数会以每100毫秒一次的频率执行
WhitneyLu wechat
Contact me by scanning my public WeChat QR code
0%