Redis读书笔记(三)
单机数据库的实现
Redis数据库
Redis数据库的实现
struct redisServer {
//...
//保存服务器中的所有数据库, 数组
redisDB *db;
//服务器的数据库数量
int dbnum;
//...
};
/**
* 客户端状态
*/
typedef struct redisClient {
//...
//记录客户端正在使用的数据库
redisDB *db;
//...
} redisClient;
过期键删除策略
- 定时删除:创建大量的定时器,从而实现定时删除策略
内存友好:通过使用定时器,定时删除可以保证过期键会尽可能快地被删除,并释放占用的内存。
CPU不友好:在过期键较多的情况下,删除过期键这一行为会占用相当一部分的CPU时间。 - 惰性删除:程序只会在取出键时才进行过期检查,并且删除已过期的键
内存不友好:如果一个键已过期,又恰好没有被访问,则一直占用内存。
CPU友好:只有在取出键时检查,不会删除任何其他无关的过期键 - 定期删除:前两种策略的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持久化的实现
命令追加
服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof-buf缓冲区的末尾。文件写入
服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区,所以在服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,考虑是否将缓冲区的内如写入和保存到AOF文件。appendfsync选项的值 flushAppendOnlyFile函数的行为 always 将aof_buf缓冲区中的所有内容写入并同步到到AOF文件 everysec 将aof_buf缓冲区中的所有内容写入到AOF文件,如果距离上次同步AOF文件的时间超过一秒钟,那么再次对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行 no 将aof_buf缓冲区中的所有内容写入到AOF文件,但不进行同步,何时同步由操作系统决定
文件的写入和同步
现代操作系统中,为了提高文件的写入效率,当用户调用write函数写入文件时,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到内存缓冲区的空间被填满或者超过了指定时间后,才真正地将缓冲区中的数据写入到磁盘里面。
AOF文件的载入与数据还原
- 创建一个不带网络连接的伪客户端,Redis的命令只能在客户端上下文中执行
- 从AOF文件中读取写命令
- 使用伪客户端执行读出的写命令
- 重复步骤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读书笔记(三)的更多相关文章
- 【转载】MDX Step by Step 读书笔记(三) - Understanding Tuples (理解元组)
1. 在 Analysis Service 分析服务中,Cube (多维数据集) 是以一个多维数据空间来呈现的.在Cube 中,每一个纬度的属性层次结构都形成了一个轴.沿着这个轴,在属性层次结构上的每 ...
- 《你必须知道的.NET》读书笔记三:体验OO之美
此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.依赖也是哲学 (1)本质诠释:“不要调用我们,我们会调用你” (2)依赖和耦合: ①无依赖,无耦合 ...
- Spring揭秘 读书笔记 三 bean的scope与FactoryBean
本书可作为王富强所著<<Spring揭秘>>一书的读书笔记 第四章 BeanFactory的xml之旅 bean的scope scope有时被翻译为"作用域&quo ...
- Struts2技术内幕 读书笔记三 表示层的困惑
表示层能有什么疑惑?很简单,我们暂时忘记所有的框架,就写一个注册的servlet来看看. index.jsp <form id="form1" name="form ...
- ES6读书笔记(三)
前言 前段时间整理了ES6的读书笔记:<ES6读书笔记(一)>,<ES6读书笔记(二)>,现在为第三篇,本篇内容包括: 一.Promise 二.Iterator和for of循 ...
- redis相关笔记(三.redis设计与实现(笔记))
redis笔记一 redis笔记二 redis笔记三 1.数据结构 1.1.简单动态字符串: 其属性有int len:长度,int free:空闲长度,char[] bur:字符数组(内容) 获取字符 ...
- [redis读书笔记] 第一部分 数据结构与对象 简单动态字符串
本读书笔记主要来自于<<redis设计与实现>> -- 黄键宏(huangz) redis主要设计了字符串,链表,字典,跳跃表,整数集合,压缩列表来做为基本的数据结构,实现键值 ...
- Mastering Web Application Development with AngularJS 读书笔记(三)
第一章笔记 (三) 一.Factories factory 方法是创建对象的另一种方式,与service相比更灵活,因为可以注册可任何任意对象创造功能.例如: myMod.factory('notif ...
- [redis读书笔记] 第三部分 多机数据库的实现 复制
另外一篇写的很好很深入的文章:http://www.tuicool.com/articles/fAnYFb : RDB持久化 http://www.tuicool.com/articles/F3Eri ...
- Redis学习笔记三:多机数据库的实现
1.复制 执行slaveof命令或者设置slaveof选项,让一个服务器去复制另外一个服务器. 旧版复制功能的实现(Redis 2.8 之前的版本) 复制功能分为同步和命令传播两个操作. 同步(syn ...
随机推荐
- Qt之新建界面动态库并使用
动态库的创建 动态库的使用 动态库的创建 //SharedLib_global.h #ifndef SHAREDLIB_GLOBAL_H #define SHAREDLIB_GLOBAL_H #inc ...
- heimaJava-网络编程
Java 网络编程 概念 网络编程可以让程序与网络上的其他设备中的程序进行数据交互 网络通信基本模式 常见的通信模式有如下两种形式,Client-Server(CS),Browser/Server(B ...
- 基于Nginx以及web服务器搭建在线视频播放
安装Nginx Nginx官网下载地址 网址打开后如图 下载windows版本的Nginx,这里下载最新的1.18.0版本 Nginx在windows下的安装只需要将其解压缩即可.建议将解压后的目录移 ...
- CSS3之动画三大特性
一 过渡模块 1 基本使用 1,过渡三要素1.1必须要有属性发生变化1.2必须告诉系统哪个属性需要执行过渡效果1.3必须告诉系统过渡效果持续时长 2.注意点当多个属性需要同时执行过渡效果时用逗号隔开即 ...
- 深入理解css 笔记(完)
一个网站,从看起来还可以,到看起来非常棒,差别在于细节.在实现了页面里 某个组件的布局并写完样式之后,不要急着继续,有意识地训练自己,以挑剔的眼光审视刚刚完成的代码.如果增加或者减少一点内边距是不是看 ...
- layui 手册
https://layui.yii666.com/doc/modules/layer.html
- 关与python面向对象的认识
面向对象编程 类:从一堆对象中以抽象的方式把相同的特征归类得到. 抽象类 类 实列 子类抽象为父类,子类继承父类特征. 类实例化为实例,实例抽象为类. class Human(object): cen ...
- python之pyqt5-第一个pyqt5程序-图像压缩工具(2.5版本,加入多线程进度条与文件drop)-小记
(如想转载,请联系博主或贴上本博地址) 题外:关于python的多线程 python因为GIL的原因,只能利用到单核CPU性能.如程序内多是计算或循环,多线程无啥意义:如程序内多IO操作,多线程可以避 ...
- redis 访问 database
edis的数据库个数是可以配置的,默认为16个,见redis.windows.conf/redis.conf的databases 16.对应数据库的索引值为0 - (databases -1),即16 ...
- sql union 和 union all
UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 但是需要注意: 1.union内部的select语句必须拥有相同数量的列. 2.列必须拥有相似的数据类型. 3.每条select语句 ...