单机数据库的实现

Redis数据库

Redis数据库的实现

struct redisServer {
//... //保存服务器中的所有数据库, 数组
redisDB *db; //服务器的数据库数量
int dbnum; //...
}; /**
* 客户端状态
*/
typedef struct redisClient {
//... //记录客户端正在使用的数据库
redisDB *db; //...
} redisClient;

过期键删除策略

  1. 定时删除:创建大量的定时器,从而实现定时删除策略

    内存友好:通过使用定时器,定时删除可以保证过期键会尽可能快地被删除,并释放占用的内存。

    CPU不友好:在过期键较多的情况下,删除过期键这一行为会占用相当一部分的CPU时间。
  2. 惰性删除:程序只会在取出键时才进行过期检查,并且删除已过期的键

    内存不友好:如果一个键已过期,又恰好没有被访问,则一直占用内存。

    CPU友好:只有在取出键时检查,不会删除任何其他无关的过期键
  3. 定期删除:前两种策略的trade-off

    定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。

    定期删除的难点:确定删除操作执行的时长和频率

Redis服务器实际使用的是惰性删除和定期删除两种策略

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

RDB持久化

  • 生成RDB文件:已过期的键不会保存到新创建的RDB文件中。
  • 载入RDB文件:如果以主服务器模式运行,载入时会对文件中保存的键进行检查,未过期的键会被载入,而过期的键则会被忽略;如果以从服务器模式运行,文件中保存的所有的键都会被载入到数据库,不过主从服务器在进行数据同步时,从服务器的数据会被清空。

AOF持久化

  • AOF文件写入:当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一条DEL命令。
  • AOF文件重写:执行AOF重写过程中,程序会对数据库中的键进行检查,已过期的键不会被保存。

复制

当服务器运行在复制模型下,从服务器的过期键删除动作由主服务器控制

  • 主服务器删除一个过期键之后,显式地向所有从服务器发送DEL命令,告知从服务器删除该过期键。
  • 从服务器在执行客户端发送的命令时,即使碰到过期键也不会删除,像处理未过期的键一样处理。
  • 从服务器只有接收到主服务器的DEL命令时,才会删除过期键。

Redis数据库通知

键空间通知(key-space notification)

关注“某个键执行了什么命令”的通知,如"SET message abc",表message键执行了set命令。

127.0.0.1:6379> SUBSCRIBE __keyspace@0__:message

1) "subscribe"                 # 订阅消息
2) "__keyspace@0__:message"
3) (integer) 1 1) "message" # 键message执行set命令
2) "__keyspace@0__:message"
3) "set" 1) "message" # 键message执行expire命令
2) "__keyspace@0__:message"
3) "expire" 1) "message" # 键message执行del命令
2) "__keyspace@0__:message"
3) "del"

键事件通知(key-event notification)

关注"某个命令被什么键执行"的通知,如"DEL message",表示DEL命令被执行message键执行。

127.0.0.1:6379> SUBSCRIBE __keyevent@0__:del

1) "subscribe"             # 订阅消息
2) "__keyspace@0__:del"
3) (integer) 1 1) "message" # 键key执行del命令
2) "__keyevent@0__:del"
3) "key" 1) "message" # 键number执行del命令
2) "__keyevent@0__:del"
3) "number" 1) "message" # 键message执行del命令
2) "__keyevent@0__:del"
3) "message"

服务器配置notify-keyspace-events选项决定服务器发送通知的类型

  • AKE: 发送所有类型的键空间通知和键事件通知
  • AK: 发送所有类型的键空间通知
  • AE: 发送所有类型的键事件通知
  • K$: 只发送和字符串键有关的键空间通知
  • El: 只发送和列表键有关的键事件通知

RDB持久化

RDB文件的创建与载入

RDB文件生成命令

  • SAVE: 阻塞Redis服务器,直到RDB文件创建完毕为止
  • BGSAVE: 派生一个子进程,由子进程负责创建RDB文件,服务器进程继续处理命令请求

RDB文件载入

Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件

AOF与RDB优先级

  • 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态
  • 只有在AOF持久化处于关闭状时,服务器才会使用RDB文件来还原数据库状态

自动间隔性保存

struct redisServer {

  //...

  // 保存条件数组
struct saveparam *saveparams; // 计数器
long long dirty; // 上一次执行保存的时间
time_t lastsave; //...
}

只要满足下面条件的任意一个(默认设置),BGSAVE命令就会被执行:

  • 服务器在900秒之内,对数据库至少1次修改
  • 服务器在300秒之内,对数据库至少10次修改
  • 服务器在60秒之内,对数据库至少10000次修改

dirty计数器

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

lastsave属性

unix时间戳,记录服务器上一次成功执行SAVE命令或BGSAVE命令的时间。

检查RDB保存条件

# 检查保存条件
def serverCron():
# ...
for saveparam in server.saveparams:
# 计算距离上次保存操作的时间
save_interval = unixtime_now() - server.lastsave if server.dirty >= savaparam.changes and save_interval > saveparam.seconds:
BGSVAE()
# ...

RDB文件结构

RDB组成部分 作用
REDIS 常量,5字节
db_version 字符串表示的整数,4字节
databases 包含任意多个数据库,以及各个数据库中的键值对数据
EOF 常量,标志RDB文件正文内容的结束,1字节
check_sum 无符号整数,程序通过上面四个部分内容计算得出的校验和,8字节

databases部分

每个database可以由三部分组成:

database组成部分 作用
SELECTDB 常量,程序遇到这个值表示接下来要读入一个数据库号码,1字节
db_number 保存着数据库号码,1字节、2字节或5字节
key_value_pairs 保存着数据库中所有的键值对

key_value_pairs部分

每个key_value_pairs部分都保存了一个或以上数量的键值对;如果键值对带有过期时间的话,那么也会保存。

不带过期时间的键值对

key_value_pairs组成部分 作用
TYPE 记录value的类型,1字节
key 键对象,总是一个字符串对象
value 值对象

带过期时间的键值对

key_value_pairs组成部分 作用
EXPIRETIME_MS 常量,告知程序接下来读入一个以毫秒为单位的过期时间,1字节
ms 记录着一个以毫秒为单位的UNIX时间戳,8字节有符号整数
TYPE 记录value的类型,1字节
key 键对象,总是一个字符串对象
value 值对象

AOF持久化

Append Only File

RDB持久化通过保存数据库中的键值对来记录数据库状态不同;AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态。

AOF持久化的实现

  1. 命令追加

    服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof-buf缓冲区的末尾。

  2. 文件写入

    服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区,所以在服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,考虑是否将缓冲区的内如写入和保存到AOF文件。

    appendfsync选项的值 flushAppendOnlyFile函数的行为
    always 将aof_buf缓冲区中的所有内容写入并同步到到AOF文件
    everysec 将aof_buf缓冲区中的所有内容写入到AOF文件,如果距离上次同步AOF文件的时间超过一秒钟,那么再次对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行
    no 将aof_buf缓冲区中的所有内容写入到AOF文件,但不进行同步,何时同步由操作系统决定

文件的写入和同步

现代操作系统中,为了提高文件的写入效率,当用户调用write函数写入文件时,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到内存缓冲区的空间被填满或者超过了指定时间后,才真正地将缓冲区中的数据写入到磁盘里面。

AOF文件的载入与数据还原

  1. 创建一个不带网络连接的伪客户端,Redis的命令只能在客户端上下文中执行
  2. 从AOF文件中读取写命令
  3. 使用伪客户端执行读出的写命令
  4. 重复步骤2,3,直到AOF文件中的所有写命令处理完毕

AOF文件重写

AOF持久化通过保存被执行的写命令来记录数据库的状态,所有随着服务器运行时间变长,AOF文件中的内容会越来越多,文件的体积也会越来越大。体积过大的AOF文件可能对Redis服务器造成影响,且AOF文件越大读入时还原数据库状态所需时间也越长。所以Redis提供了AOF文件重写功能。

AOF文件重写功能

AOF文件重写功能通过读取服务器当前的数据库状态来实现

  • Redis服务器创建一个新的AOF文件代替现有的AOF文件
  • 新旧AOF文件保存的数据库状态相同,但新AOF文件不会包含任何冗余命令
  • 为了避免造成客户端输入缓冲区溢出,重写程序在处理列表、集合、有序集合、哈希表可能存在多个元素的键时,会先检查元素的数量是否超过了REDIS_AOF_REWRITE_ITEMS_PER_CMD常量值,如果超过了则会使用多条命令;如果没超过则使用1条命令即可

AOF后台重写

Redis将AOF重写程序放到子进程执行的优势:

  • 子进程进行AOF重写期间,服务器进程可以继续处理命令请求
  • 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性

AOF重写过程中数据库状态发送变化,如何保证重写后的AOF文件和当前数据库状态的一致性:

  • Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在子进程创建之后开始使用,当Redis服务器执行完一下写命令后,它会同时将写命令发送给AOF缓冲区和AOF重写缓冲区
  • AOF缓冲区的内容会被定期写入和同步到AOF文件,对当前的AOF文件处理如常进行
  • AOF重写缓冲区的所有内容会被写入重写后的AOF文件,此时新AOF文件保存的数据库状态就和当前数据库一致
  • 最后,对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧文件的替换

事件

文件事件

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

文件事件内部结构

typedef struct aeFileEvent {

  // 监听事件类型:AE_READABLE, AE_WRITABLE, AE_READABLE | AE_WRITABLE
int mask; // 读事件处理器
aeFileProc* rfileProc; // 写事件处理器
aeFileProc* wfileProc; // 多路复用库的私有数据
void* clientData;
} aeFileEvent;

文件事件处理器

  • Redis基于Reactor模式开发了自己的网络事件处理器。
  • 文件处理器使用I/O多路复用程序来同时监听多个套接字。
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与之对应的文件事件就会产生,文件处理器则会调用套接字关联好的事件处理器来处理事件。

I/O多路复用

I/O多路复用是针对单线程并发的,多路是指多个网络连接,复用是指使用同一个线程。

文件事件类型

  • 当有Client申请socket连接时,会注册一个AE_READABLE事件
  • 当接受Client命令请求时,会注册一个AE_READABLE事件
  • 当服务器返回处理结果时,会注册一个AE_WRITEABLE事件

事件的处理器

  • 连接应答处理器:acceptTcpHandler函数
  • 命令请求处理器:readQueryFromClient函数
  • 命令回复处理器:sendReplyToClient函数

时间事件

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

时间事件内部结构

typedef struct aeTimeEvent {

  // 服务器为时间事件创建全局唯一ID, 从小到大自增
long long id; // 记录什么时候执行该事件
long when_sec;
long when_ms; // 时间事件处理器
aeTimeProc* timeProc; void* clientData; struct aeTimeEvent* prev;
struct aeTimeEvent* next; //双向链表
} aeTimeEvent;

时间事件类型

  • 定时事件:让一段程序在指定时间后执行一次
  • 周期事件:让一段程序每隔指定时间执行一次

时间事件执行

  • 服务器将所有时间事件都放在一个无序列表(没有按照事件执行时间排序, 即when属性),每当时间事件执行器运行时,则遍历整个链表,查找所有已到达的时间事件并调用对应的处理器。
  • 正常模式下Redis服务器只有serverCron一个时间事件,所以无序列表不会影响时间事件执行的性能。

事件的调度与执行

服务器中同时存在文件事件和时间事件,所以服务器需要对这两种事件进行调度,决定何时处理文件事件、何时处理时间事件。

调度函数伪代码

def aeProcessEvents():
time_event = aeSearchNearestTimer() # 获取离当前时间最接近的时间事件
remained_ms = time_event.when - unix_ms_now() # 如果事件已到达,则设为0
if remained_ms < 0:
remained_ms = 0 timeval = create_timeval_with_ms(remained_ms) # 阻塞并等待文件事件产生,最大阻塞时间由传入的timeval决定
aeApiPoll(timeval) processFileEvents() # 处理所有已产生的文件事件
processTimeEvents() # 处理所有已达到的时间事件

Attention

  • 文件事件和时间事件的处理都是同步、有序、原子地执行,服务器不会中断事件处理,也不会对事件抢占。
  • 无论是文件事件还是时间事件的处理器,都会尽可能减少程序的阻塞时间,并在需要时主动让出执行权,从而降低事件饥饿的可能性。
  • 如命令回复处理器将回复写入到客户端套接字时,如果写入数据超过一个预设常量则会主动用break跳出循环,将余下的数据留到下次再写

Redis读书笔记(三)的更多相关文章

  1. 【转载】MDX Step by Step 读书笔记(三) - Understanding Tuples (理解元组)

    1. 在 Analysis Service 分析服务中,Cube (多维数据集) 是以一个多维数据空间来呈现的.在Cube 中,每一个纬度的属性层次结构都形成了一个轴.沿着这个轴,在属性层次结构上的每 ...

  2. 《你必须知道的.NET》读书笔记三:体验OO之美

    此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.依赖也是哲学 (1)本质诠释:“不要调用我们,我们会调用你” (2)依赖和耦合: ①无依赖,无耦合 ...

  3. Spring揭秘 读书笔记 三 bean的scope与FactoryBean

    本书可作为王富强所著<<Spring揭秘>>一书的读书笔记  第四章 BeanFactory的xml之旅 bean的scope scope有时被翻译为"作用域&quo ...

  4. Struts2技术内幕 读书笔记三 表示层的困惑

    表示层能有什么疑惑?很简单,我们暂时忘记所有的框架,就写一个注册的servlet来看看. index.jsp <form id="form1" name="form ...

  5. ES6读书笔记(三)

    前言 前段时间整理了ES6的读书笔记:<ES6读书笔记(一)>,<ES6读书笔记(二)>,现在为第三篇,本篇内容包括: 一.Promise 二.Iterator和for of循 ...

  6. redis相关笔记(三.redis设计与实现(笔记))

    redis笔记一 redis笔记二 redis笔记三 1.数据结构 1.1.简单动态字符串: 其属性有int len:长度,int free:空闲长度,char[] bur:字符数组(内容) 获取字符 ...

  7. [redis读书笔记] 第一部分 数据结构与对象 简单动态字符串

    本读书笔记主要来自于<<redis设计与实现>> -- 黄键宏(huangz) redis主要设计了字符串,链表,字典,跳跃表,整数集合,压缩列表来做为基本的数据结构,实现键值 ...

  8. Mastering Web Application Development with AngularJS 读书笔记(三)

    第一章笔记 (三) 一.Factories factory 方法是创建对象的另一种方式,与service相比更灵活,因为可以注册可任何任意对象创造功能.例如: myMod.factory('notif ...

  9. [redis读书笔记] 第三部分 多机数据库的实现 复制

    另外一篇写的很好很深入的文章:http://www.tuicool.com/articles/fAnYFb : RDB持久化 http://www.tuicool.com/articles/F3Eri ...

  10. Redis学习笔记三:多机数据库的实现

    1.复制 执行slaveof命令或者设置slaveof选项,让一个服务器去复制另外一个服务器. 旧版复制功能的实现(Redis 2.8 之前的版本) 复制功能分为同步和命令传播两个操作. 同步(syn ...

随机推荐

  1. Qt之新建界面动态库并使用

    动态库的创建 动态库的使用 动态库的创建 //SharedLib_global.h #ifndef SHAREDLIB_GLOBAL_H #define SHAREDLIB_GLOBAL_H #inc ...

  2. heimaJava-网络编程

    Java 网络编程 概念 网络编程可以让程序与网络上的其他设备中的程序进行数据交互 网络通信基本模式 常见的通信模式有如下两种形式,Client-Server(CS),Browser/Server(B ...

  3. 基于Nginx以及web服务器搭建在线视频播放

    安装Nginx Nginx官网下载地址 网址打开后如图 下载windows版本的Nginx,这里下载最新的1.18.0版本 Nginx在windows下的安装只需要将其解压缩即可.建议将解压后的目录移 ...

  4. CSS3之动画三大特性

    一 过渡模块 1 基本使用 1,过渡三要素1.1必须要有属性发生变化1.2必须告诉系统哪个属性需要执行过渡效果1.3必须告诉系统过渡效果持续时长 2.注意点当多个属性需要同时执行过渡效果时用逗号隔开即 ...

  5. 深入理解css 笔记(完)

    一个网站,从看起来还可以,到看起来非常棒,差别在于细节.在实现了页面里 某个组件的布局并写完样式之后,不要急着继续,有意识地训练自己,以挑剔的眼光审视刚刚完成的代码.如果增加或者减少一点内边距是不是看 ...

  6. layui 手册

    https://layui.yii666.com/doc/modules/layer.html

  7. 关与python面向对象的认识

    面向对象编程 类:从一堆对象中以抽象的方式把相同的特征归类得到. 抽象类 类 实列 子类抽象为父类,子类继承父类特征. 类实例化为实例,实例抽象为类. class Human(object): cen ...

  8. python之pyqt5-第一个pyqt5程序-图像压缩工具(2.5版本,加入多线程进度条与文件drop)-小记

    (如想转载,请联系博主或贴上本博地址) 题外:关于python的多线程 python因为GIL的原因,只能利用到单核CPU性能.如程序内多是计算或循环,多线程无啥意义:如程序内多IO操作,多线程可以避 ...

  9. redis 访问 database

    edis的数据库个数是可以配置的,默认为16个,见redis.windows.conf/redis.conf的databases 16.对应数据库的索引值为0 - (databases -1),即16 ...

  10. sql union 和 union all

    UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 但是需要注意: 1.union内部的select语句必须拥有相同数量的列. 2.列必须拥有相似的数据类型. 3.每条select语句 ...