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 ...
随机推荐
- Matlab|multcompare文档
multcompare Syntax c = multcompare(stats) c = multcompare(stats,Name,Value) [c,m] = multcompare(___) ...
- js 获取html加载的参数?file=123&time=2021
<script> function GetArgsFromHref(sHref, sArgName) { var args = sHref.split("?"); va ...
- VUE keepAlive缓存问题之动态使用exclude(会使用到VUEX)
exclude是啥? 官方解释: 怎么用呢? 处理的问题是什么?(答:返回首页的时候清除B页面的缓存) 我遇到的问题是: 一开始状态:A(首页). B(列表).C(列表中的详情)三个页面,设置B页面的 ...
- js数组常用的方法
var arr=['hello','前端','world']; 1. arr.join(分隔符):将数组中的值拼接成一个字符串,返回这个字符串,默认分隔符"," arr.join( ...
- AJAX请求的基本操作
1 const { request, response } = require('express'); 2 //引入express 3 const express = require('express ...
- 关于Centos7Th 初始化的一些概述
- 概述 Q:为什么要初始化,什么是初始化? A:一般初始化是根据的后期要部署的业务环境来定制的,新装的系统其自带的软件不够支撑各种开发环境或者运维工作:需要部署和设置对应的安全环境.开发/运维软件. ...
- 替换yum源
1.yum源进行备份 进入到yum源的配置文件中 执行命令如下:cd /etc/yum.repos.d 将yum源进行备份:mv Centos-Base.repo Centos-Base.repo.b ...
- C#清空控件的值
/// 清除容器里面某些控件的值 /// </summary> /// <param name="parContainer">容器类控件</param ...
- Hive使用Tez作为计算引擎,hive启动报错
1.问题描述: (1)问题示例: 1)hive使用配置文件hive-site.xml配置tez为计算引擎,hive登录报错: [Hadoop@master Tmp]$ hiveHive Session ...
- System.IO.IOException:“找不到资源“views.buttonstylepage.xaml”。”
初学作为记录(事发场景): WPFDemo的程序集中,定义了一个Views文件夹,该文件夹放一些页面Page.UI层面的东西.用Frame空间做导航的时候,始终报一个错误 // System. ...