单机数据库实现

九、数据库

1.服务器中的数据库

一个redis服务器保存多个数据库。

struct redisServer {
//一个数组,多个数据库
redisDb *db;
}

当执行select 1,就是切换数据库到db[1],具体就是会修改redisClient.db指针到redisServer.db[1]

2.数据库键空间

typedef struct redisDb{

dict *dict;//数据库键空间
dict *expires;//过期时间

}

这里的dict就是上面说的字典数据结构。

这个字典的key就是redis里面的key,每个key都是字符串对象

值就是数据库的值,可以是字符串对象,列表对象,哈希对象,集合对象,游戏集合对象。

3.键的过期时间

如果我们对一个键设置过期时间

redis就会在字典expires里面,加上key=过期的时间戳(精确到毫秒)。

执行ttl,redis会比较expires里面的时间戳和当前时间的差值,然后返回差值。

过期key的判定

  • 当访问key时,redis会检查key是否过期,如果是,删除key,并报错key不存在
  • 对于一直没有访问的key,redis会定期扫描expires里面的key,判定key是否过期,如果是,就删除key

十、RDB持久化

存在内存中的数据,称为数据库状态

持久化就是把数据库状态,保存为RDB文件,RDB文件是存在硬盘的。

1.客户端发起保存

执行命令save,bgsave,可以立刻把数据库状态保存为RDB文件。

  • save命令是阻塞的,即执行过程中,服务器不能处理其他客户端的请求。
  • bgsave是异步的,redis会启动一个新进程,把内存的数据都复制到新进程,然后执行保存数据到RDB文件的工作,而原进程就继续处理客户端的请求

2.服务端定期保存

redis也会定期执行保存操作

服务器的配置:

save 900 1

表示服务器在900秒内,对数据库执行了至少一次修改,服务器就会执行保存操作

如果有多个svae配置,它们直接的关系是或的关系,即满足其中一个,就会执行保存

struct redisServer{
long long dirty;
time_t lastsave
}

数据库对象中,有两个属性:

  • dirty,记录距离上一次保存操作后,数据库执行了多少次修改。
  • lastsave,上一次保存操作的时间戳

redis通过这两个属性来实现定期保存的机制

3.RDB文件结构

RDB文件由

REDIS db_version databases EOF cehcks_sum

构成

  • redis是一个字符串,
  • db_version是一个4字节的int类型,表示数据库的版本号
  • databases 表示数据库数据
  • EOF 1字节表示文件的结束
  • check_sum表示前面的数据的md5

1.数据库数据

databases部分的构成:

SELECTDB db_number PAIRS

  • SELECTDB是1字节,常量
  • db_number 是数据库编号
  • PAIRS是数据库数据里面的键值对

2.键值对

键值对构成

EXPIRETIME_MS ms TYPE KEY VALUE

  • EXPIRETIME_MS 1字节常量,表示这个键有超时时间,可选
  • ms 超时时间的时间戳
  • TYPE 1字节常量,表示键的类型,redis会根据这个常量,来决定怎么解析后面的KEY和VALUE
  • KEY 是一个字符串对象,存储方法和VALUE的字符串对象一样
  • 值的对象

3.VALUE编码

redis会根据TYPE这个常量,来决定怎么读取VALUE的数据。

KEY肯定就是字符串类型了。

3.1字符串对象

如果TYPE=REDIS_ENCODING_STRING,表示这个对象是数值字符串对象

字符串对象有以下三种存储类型

int类型

构成:

ENCODING  int
  • ENCODING 1字节常量,表示int类型,例如16位还是32位
  • int 数字

无压缩字符串

如果TYPE=REDIS_ENCODING_RAW,表示这个是普通字符串

len string
  • len 字符串的长度
  • string 字符串的值

压缩字符串

如果字符串的长度大于20字节,就会压缩字符串。

REDIS_RDB_ENC_LZF  compressed_len origin_len compressed_string
  • REDIS_RDB_ENC_LZF 1字节常量,压缩的算法
  • compressed_len 压缩后的长度
  • origin_len 原字符串长度
  • compressed_string 压缩后的内容

问题:

程序怎么知道这是int类型,还是无压缩字符串,还是压缩字符串的?

3.2列表对象

当TYPE=REDIS_RDB_TYPE_LIST 表示这是一个列表对象

list_length item1 item2 itemN
  • list_length 列表的长度
  • item1-N 列表的元素,都是字符串对象

3.3集合对象

如果TYPE=REDIS_RDB_TYPE_SET 那么表示这是一个集合对象

set_size elem1 ... elemN

  • set_size集合的长度
  • elem1 表示集合的元素,字符串对象

3.4哈希表对象

如果TYPE=REDIS_RDB_TYPE_HASH 那么表示这是一个哈希表对象

hash_size key_value_pair1 。。。。。。key_value_pairN
  • hash_size 哈希表的键值对数量
  • key_value_pair1 键值对的值

键值对的构成

key1 value1 key2 value2
  • key1 键值对的键,字符串对象
  • value1 键值对的值,字符串对象

3.5 有序集合对象

如果TYPE=REDIS_RDB_TYPE_ZSET,表示这是一个有序集合对象

sorted_set_size element1 。。。。  elementN
  • sorted_set_size元素的数量
  • element1 元素

每个元素的构成

member1 score1

  • member1 元素的内容,字符串对象
  • score1 元素的分值,redis会把int类型或者float类型转换为字符串类型保存

4.RDB文件例子

REDIS 0 0 0 6 376\0\0 003 MSG 005 HELLO 377 207z=304fTL

343

  • REDIS
  • 0006是版本号
  • 376是SELECTDB常量
  • \0是db 0
  • \0是字符串类型
  • 003 MSG表示字符串MSG
  • 005 HELLO 表示字符串HELLO
  • 377是EOF
  • 后面是md5

5.读入RDB文件

在Redis启动的时候,会自动加载RDB文件,加载成功后,服务器才处理客户端的请求。

十一、AOF持久化

不同于RDB一次存储整个数据库状态

AOF(Append Only File)是每次执行写命令,就append一条指令到文件。

1.命令追加

struct redisServer {

	sds aof_buf;
}

在redis 服务器对象中,有一个aof_buf属性,用于存储AOF命令

每次服务器执行写命令的时候,都会往这个缓冲区写AOF命令。

在Redis的时间事件中,会调用flshAppendOnlyFile函数,决定是否吧缓冲区的数据flush到文件。

例如如果执行命令 set key value

就会产生下面的AOF命令:

*3\4\n$3\r\nSET\4\n$3\r\nKEY\r\n$5\r\nVALUE\r\n

2.AOF文件载入

在启动REDIS的时候,会加载AOF文件

3.AOF重写

当写命令很多的时候,甚至都是操作很少的几个键的话,例如不断地修改一个key的值,这样就会有很多命令。

这时候就可以执行重写命令,来把AOF命令压缩。

例如命令:

set test a
set test b
set test c

会被压缩为

set test c

因为上面的两条命令就没意义了。

实际的重写进程,不会去扫描AOF文件,而是会扫描数据库的键值对,然后执行SET或者push等命令。

4.AOF后台重写

当执行BGREWRITEAOF命令,Redis就会执行后台重写

  • 启动一个新的进程,把数据库状态复制过去,执行重写操作,也就是扫描所有数据库,里面的所有key
  • 原进程继续处理客户端请求,写操作写入到缓冲区
  • 新进程执行完重写操作后,生成新的AOF文件,发送信号给原进程
  • 原进程收到信号后,把把缓冲区的命令append到新的AOF文件,然后当前的AOF文件切换为新的AOF文件

十二、事件

redis是两种事件

  • 文件事件,就是处理客户端的请求
  • 时间时间,处理redis自身的一些定时任务

1.文件事件

Redis使用IO多路复用的方式,当有客户端的请求(例如Socket有数据可以读取,有数据可以写),就会生成一个事件,塞到处理队列里面

每当Redis执行文件事件的时候,就从中取一个事件来执行。

事件包含

  • Socket号
  • 客户端实例

2.时间事件

redis里面会有一个时间事件队列

一个时间事件包含

  • id 事件的id
  • when 什么时候执行,时间精确到毫秒
  • timeproc 执行函数

具体实现就是,redis会每个100毫秒,就去这个队列里面遍历,看哪个时间可以执行(when小于当前时间),如果可以就执行。

  • 如果是定期事件,执行完,就会从队列里面删除
  • 如果是周期性事件,执行完,删除事件后,会创建一个新的事件插入到队列

其实现在Redis只有一个事件事件,就是serverCron

这个事件会执行:

  • 更新服务器的各类统计信息
  • 清理数据库中过期的键值对
  • 关闭和清理失效的客户端
  • 尝试进行RDB和AOF的持久化操作
  • 如果是主服务器,对从服务器进行定期同步
  • 如果是集群模式,对集群进行定期同步和连接测试

serverCron每秒运行10次,也就是每100毫秒一次。

3. 事件的调度

当Redis服务器启动后,跑完了初始化的任务,就会死循环得跑下面这个流程:

def aeProcessEvents():
time_event=aeSearchNearestTimer() #寻找最近的时间事件
remaind_ms=time_event.when-unix_ts_now() #计算事件要在多少毫秒后执行
if remaind_ms<0:
remaind_ms=0
timeval=create_timeval_with_ms(remaind_ms) #通过remaind_ms计算最长阻塞时间
aeApiPoll(timeval) #等待文件事件,超时时间为最长阻塞时间,如果remaind_ms=0,就马上返回,不阻塞
processFileEvents() #执行文件事件
processTimeEvents() #执行时间事件

所以总的来说

  • 会阻塞进程来等待文件事件
  • 阻塞的时间不会超过下个时间事件的执行时间

这样可以保证

  • 时间事件可以尽量准时(不是完全准时)地被执行
  • 文件事件也可以及时处理

十三、客户端

Redis的服务端对象有一个列表,保存所有的客户端对象,每个连接过来的客户端,都会创建一个客户端实例。

struct redisServer {

	list *clients;

}

redis命令 client list 可以查看所有连接的客户端。

客户端的属性有:

  1. flags,使用不同的标志来表示客户端的角色,例如是普通的客户端,还是主从同步的客户端等等
  2. 输入缓冲区,客户端发来的数据,首先放到这里,然后再进行处理
  3. argv,argc,命令的参数值,和数量
  4. 输出缓冲区,有两个,一个是固定大小的16KB,一个是可变大小的
  5. 套接字ID,
  6. 身份验证,记录客户端是否进行了身份验证,如果否,不能执行除auth外的其他命令
  7. 命令实现函数,命令名和实现函数的映射,这个应该是全局统一的,不是每个客户端都有一个的
  8. 名字,客户端可以自己设置名字,如果没有设置,为NULL
  9. 创建时间,客户端的创建时间戳
  10. 上一次执行命令的时间,用于计算空转时间

客户端的生命周期:

  1. 创建,当客户端连接上服务端后,服务端就会创建一个客户端实例,添加到clients列表的后面
  2. 客户端发送命令给服务端
    1. 发送的命令首先存储到输入缓冲区,然后生成文件事件E1
  3. 服务端处理命令
    1. 当Redis主进程执行该文件事件E1时,就会解析输入缓冲区,解析里面的参数,和参数个数,存储到argv和argc。例如如果执行命令 set test aa,参数就是['set','test','aa'],个数是3
    2. 解析后,根据argv[0],在命令实现函数里面寻找set命令对应的执行函数,命令部分大小写
    3. 调用执行函数,传入client对象
    4. 执行函数里面,执行相应的操作,把返回结果,存储到输出缓冲区。根据返回结果的大小,决定存储到哪个缓冲区,如果大小超出服务器的限制,就直接关闭客户端。
    5. 生成套接字可写的文件事件E2
    6. 结束当前的文件事件
  4. 服务端返回命令的结果
    1. 当服务端执行文件事件E2时,把输出缓冲区的数据,传输给客户端
  5. 关闭客户端
    1. 当出现以下情况,会关闭客户端

      1. 客户端进程退出或者杀死,这样网络连接(Socket)就会被关闭,客户端也会被关闭
      2. 客户端发送不符合协议格式的请求
      3. 客户端成功CLIENT KILL命令的目标
      4. 如果服务器设置了timeout属性(客户端的超时时间),而客户端的空转时间超过timeout。如果客户端在执行BLPOP,订阅等命令,就不会被关闭。
      5. 发送的数据或者返回的数据超出缓冲区的限制

十四、服务器

1. 执行set命令的整个流程

参考上面的客户端生命周期

2.serverCron函数

参考时间事件里面的serverCron说明。

除此之外还有:

  1. 更新时间缓存,Redis里面有很多地方要用到服务器时间,对于时间精度要求不高的地方,Redis会使用时间缓存,而不是再去调用系统函数。
  2. 更新LRU时钟,
  3. 更新服务器每秒执行命令次数
  4. 更新服务器内存峰值
  5. 处理SIGTERM信号。启动服务器的时候,Redis会监听SIGTERM信号,当收到信息后,会把shutdown_asap属性置为1,在serverCront中,如果这个属性是1,就会关闭服务器
  6. 管理客户端资源
    1. 如果客户端连接超时,关闭客户端
  7. 管理数据库资源,例如删除过期的键,对字典进行收缩操作
  8. 执行被延迟的BGREWRITEAOF操作
  9. 检查持久化操作的状态
  10. 将AOF缓冲区的内容写入AOF文件
  11. 增加cronloops计数器,这个计数器记录了serverCron被执行的次数

3.初始化服务器

当启动服务器的时候,服务器会做以下操作

  1. 初始化状态结构
  2. 载入配置选项
  3. 初始化服务器数据结构,例如服务器实例,例如共享内存的数据
  4. 还原数据库状态,即载入RDB文件或者AOF文件
  5. 执行事件循环,就是事件章节中的死循环

《Redis 设计与实现》读书笔记(二)的更多相关文章

  1. 《CSS世界》笔记二:盒模型四大家族

    上一篇:<CSS世界>笔记一:流/元素/尺寸下一篇:<CSS世界>笔记三:内联元素与对齐 写在前面 在读<CSS世界>第四章之前,粗浅的认为盒模型无非是margin ...

  2. CSS揭秘读书笔记 (一)

    CSS揭秘读书笔记      (一) 一.半透明边框 要想实现半透明边框可以使用border: border: 10px  solid  hsla(0,0%,100%,.5); background: ...

  3. 《你必须知道的.NET》读书笔记二:小OO有大原则

    此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.单一职责原则 (1)核心思想:一个类最好只做一件事,只有一个引起它变化的原因 (2)常用模式:Fa ...

  4. spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定

    本文是王福强所著<<spring揭秘>>一书的读书笔记 我们前面就说过,Spring的IoC容器时一个IoC Service Provider,而且IoC Service Pr ...

  5. ES6读书笔记(二)

    前言 前段时间整理了ES6的读书笔记:<ES6读书笔记(一)>,现在为第二篇,本篇内容包括: 一.数组扩展 二.对象扩展 三.函数扩展 四.Set和Map数据结构 五.Reflect 本文 ...

  6. 《精通CSS》读书笔记(一)

    最近新添16本书,目前开始看陈剑瓯翻译的<精通CSS——高级Web标准解决方案>(Andy Budd, CSS Mastery -- Advanced Web Standards Solu ...

  7. spring揭秘 读书笔记 二 BeanFactory的对象注冊与依赖绑定

    本文是王福强所著<<spring揭秘>>一书的读书笔记 我们前面就说过,Spring的IoC容器时一个IoC Service Provider,并且IoC Service Pr ...

  8. 【记】《.net之美》之读书笔记(二) C#中的泛型

    前言 上一篇读书笔记,很多小伙伴说这本书很不错,所以趁着国庆假期,继续我的读书之旅,来跟随书中作者一起温习并掌握第二章的内容吧. 一.理解泛型 1.为什么要使用泛型?-----通过使用泛型,可以极大地 ...

  9. Mastering Web Application Development with AngularJS 读书笔记(二)

    第一章笔记 (二) 一.scopes的层级和事件系统(the eventing system) 在层级中管理的scopes可以被用做事件总线.AngularJS 允许我们去传播已经命名的事件用一种有效 ...

  10. how tomcat works 读书笔记(二)----------一个简单的servlet容器

    app1 (建议读者在看本章之前,先看how tomcat works 读书笔记(一)----------一个简单的web服务器 http://blog.csdn.net/dlf123321/arti ...

随机推荐

  1. Reactor系列(八)concatMap有序映射

    #java#reactor#comcatMap# 有序映射 视频讲解:https://www.bilibili.com/video/av79705356/ FluxMonoTestCase.java ...

  2. poj1915(双向bfs)

    题目链接:https://vjudge.net/problem/POJ-1915 题意:求棋盘上起点到终点最少的步数. 思路:双向广搜模板题,但玄学的是我的代码G++会wa,C++过了,没找到原因QA ...

  3. 原生js实现图片的3d效果

    <!doctype html><html lang="en"><head><meta charset="UTF-8"& ...

  4. Linux中实用的命令

    1. 查看linux机器是32位还是64位的方法: 1.file  /sbin/init 或者file  /bin/ls           (注意命令中的空格) /sbin/init: ELF64- ...

  5. (三)mysql SQL 基本操作

    文章目录 MySQL服务器对象 mysql 的基本操作 SQL的注释 库操作 表(字段)操作 数据操作 MySQL服务器对象 mysql 服务器对象内部分成了 4 层: 系统(DBMS)----> ...

  6. Escape(多记一个方向状态的BFS)迷宫逃脱

    题意:https://www.nitacm.com/problem_show.php?pid=2266 vis记[x][y][dir]三个状态就行. 引用:https://blog.csdn.net/ ...

  7. 怎样判断浏览器是否支持canvas

    1. 如果网页必须使用canvas, 则需要告知用户更换或更新浏览器. 这时可以通过在<canvas>标签之间添加替代元素进行 <canvas id="c1"&g ...

  8. 基于create-react-app脚手架,按需加载antd组件以及less样式

    摘要 为了更好的书写css样式,在react中引入less,在网上查询了许多关于react引入less样式文件的资料,大多数都是需要在react项目中npm run eject暴露出底层文件,然后在底 ...

  9. jQuery获取的dom对象和原生的dom对象有何区别

    js原生获取的dom是一个对象,jQuery对象就是一个数组对象,其实就是选择出来的元素的数组集合,所以说他们两者是不同的对象类型不等价 原生DOM对象转jQuery对象 var box = docu ...

  10. 使用js输出1000以内的水仙花数

    什么是水仙花数 水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI).自恋数.自幂数.阿姆斯壮数或阿姆斯特 ...