简读笔记-Redis设计与实现第二章

第二部分 单机数据库的实现

数据库


服务器中的数据库

  • Redis服务器的全部数据库都保存在redisServer.db数组中,而数据库的数量使用redisServer.dbnum属性保存

切换数据库

  • 客户端经过修改目标数据库指针,让它指向redisServer.db数组中的不一样元素来切换不一样的数据库

数据库键空间

  • 数据库主要由dict和expires两个字典域构成,其中dict字典负责保存键值对,而expires字典则负责键的过时时间
  • 由于数据库由字典构成,所以对数据库的操做都是创建在对字典操做之上
  • 数据库的键老是一个字符串对象,而值则能够是任意一种Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象、有序集合对象。

设置键的过时时间java

  • expires字典的键指向数据库中的某个键,而值则记录了数据库键的过时时间,过时时间以毫秒为单位的UNIX时间戳

过时键删除策略

  • 三种不一样的删除策略redis

    • 定时删除算法

      • 在设置一个键的同时,建立一个定时器,让定时器在键过时时间来临时,当即执行对键的删除操做
      • 优势: 对内存友好,能尽快地将过时键占用的内存释放
      • 缺点: 对时间不友好,若是过时键不少,那么会占用大量CPU时间,影响服务器响应时间和吞吐量
    • 惰性删除数据库

      • 听任过时键无论,可是每次从键空间中获取键时,都检查取得的键是否过时,若是过时,就删除该键;若是没有过时,就返回该键
      • 优势:对时间友好,只有当取出过时键时,才将该键删除
      • 缺点:对空间不友好,大量过时无用键占用内存,由内存泄露的风险
    • 按期删除数组

      • 每一个一段时间,程序就对数据库进行一次检查,删除里面的过时键,至于要删除多少过时键,以及要检查多少个数据库,由算法决定
      • 优势: 对上面两种策略的折衷。 对内存友好,对空间友好
      • 关键是如何决定删除操做执行的时常和频率
  • Redis的过时键删除策略缓存

    • Redis使用的是 按期删除 + 惰性删除 保证过时键必定能被删除。并合理利用CPU时间和避免内存空间浪费
    • 惰性删除 : 在执行命令以前,对输入的键进行过时检查
    • 按期删除 : 在规定时间内,分屡次遍历服务器中多个数据库,从数据库中的expires字典随机检查一部分键的过时时间,并删除其中的过时键。

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

  • 执行SAVE命令或者BGSAVE命令所产生的新RDB文件不会包含已过时的键
  • 执行BGREWRITEAOF命令所产生的重写AOF文件不会包含已过时的键安全

    • 当一个过时键被删除以后,服务器会追加一条DEL命令到现有的AOF文件末尾,显示地删除过时键
  • 当载入RDB or AOF文件时,会对文件保存的键进行检查,过时的键会被忽略。
  • 从服务器即便发现过时键也不会主动删除,而是等待主节点发来DEL命令,这种统1、中心化的过时键删除策略能够保证主从服务器的数据一致性。

数据库通知

  • 当Redis命令对数据库进行修改以后 , 服务器会根据配置向客户端发出数据库通知 (PUB/SUB)服务器

    • 键空间通知: 某个键执行了什么命令(SET / EXPIRE / DEL)
  • 键事件通知: 某个命令被哪些键执行了 (KEY1 / KEY2 / KEY3)

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文件中全部命令都是以Redis命令请求协议的格式(文本协议)保存的
  • 命令请求会先保存到AOF缓冲区里面,以后再按期写入并同步到AOF文件中

    • 因为内存和磁盘的输入/输出速度不匹配,所以会将数据先写入缓冲区。系统提供了fsync, fdatasync两个同步函数(系统调用),让操做系统当即将缓冲区的数据写入硬盘中,减小缓冲区因为宕机而丢失数据的影响
  • appendfsync选项的不通值对AOF持久化功能的安全性和Redis服务器的性能有很大的影响

    • always : 每一个事件循环都将aof_buf缓冲区内容写入同步到AOF文件
    • everysec(默认) : 每一个事件循环后,判断上一次AOF是否间隔1S,若是是,则将aof_buf缓冲区内容写入同步到AOF文件。 所以就算故障停机,缓存也只丢失1S的数据。
    • no : 何时将缓冲区内容同步到AOF文件中,由操做系统决定

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事件与命令回复处理器之间的关联。

时间事件

  • 时间事件分为定时事件和周期性事件;定时事件只在指定时间到达一次,而周期性事件则每隔一段事件到达一次。
  • 服务器在通常状况下只执行serverCorn函数一个时间事件,而且是周期性的(100ms一次)

    事件实现的三个属性:
    id:时间事件全局ID    ,  when:事件到达时间    timeProc:事件处理函数
    与一个由事件节点构成的无序链表

事件的调度与执行

  • 文件事件和时间事件之间是合做关系,服务器会轮流处理这两种事件,而且处理事件过程当中不会发生抢占
  • 时间事件的实际处理事件一般会比设定的到达晚一些(由于没法中断文件事件)
  • ServerCron是Redis周期性事件的主要函数。 它的工做主要包括

    • 更新服务器的各种统计信息,如时间,内存占用
    • 清理数据库过时键值对
    • 尝试进行AOF和RDB操做等等

客户端

  • 服务器状态结构使用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目标 ; 空转时间超时 ; 输出缓冲区的大小超出限制.

服务端

  • 一个命令请求从发送到完成要经历的步骤:

    • 客户端将命令请求发给服务器
    • 服务器读取命令请求,并分析命令参数
    • 命令执行器根据参数查找命令的实现函数,而后执行实现函数并得出命令回复

      • 执行预备操做: 如检验命令的格式 ; 内存是否足够 ; 命令此时是否合法 ; 查看是否开启事务
      • 调用命令实现函数
      • 执行后续操做: 更改统计信息,如耗费时长 ; 若是开启了AOF还要往缓冲区写数据 ; 若是它是master,那么还要将数据同步到从服务器
    • 服务器将命令回复返回给客户端
  • ServerCron函数(每隔100ms执行一次,维护服务器相关资源,并作统计)

    • 更新服务器时间缓存
    • 更新LRU时钟 (空转时间 = LRU时钟 - 某个键上次访问时间 )
    • 更新服务器每秒执行的命令数 (统计吞吐量) ; 更新内存峰值
    • 处理SIGTERM信号(中断信号)
    • 管理数据库资源(检查过时键)
    • 将AOF缓冲区内容写入AOF (每次事件循环时都会作出检查)
  • 服务器从启动到可以处理客户端请求通过的步骤

    • 初始化服务器状态
    • 载入服务器配置
    • 初始化服务器数据结构
    • 还原数据库状态
    • 执行事件循环
相关文章
相关标签/搜索