《Redis设计与实现》第一版 学习记录

Redis

Redis ,一个用C写的基于内存的键值数据库软件。与传统的关系型数据库产品最大的不同是,Redis基于内存,读写速度快(尤其是读,磁盘的顺序写倒也不慢)。

1 数据结构与对象

redis 的键(key)只有1种数据类型 string,而值(value)有5种数据类型

  1. string 字符串
  2. list 列表
  3. hash 哈希
  4. set 集合
  5. zset 有序集 1

1.1 string字符串类型

string 有两种实现:

1.1.1 sds

当值是字符串类型时,redis使用sds类型来存储。sds (Simple Dynamic String) 简单动态字符串。redis很少直接使用C的char*来表示字符串,而是使用sds——redis的一种字符串实现——来取代char*。

struct sdshdr{
	// 当前字符串占用的长度
	int len
	// 分配给sdshdr的总长度-字符串占用的铲毒=剩余空间
	int free
	// 字符串的内容
	char* buf
	// buf为"Hello World\0" '/0'为结束符,所以len(buf)=len+1
}

从sdshdr的实现就能看出,sdshdr比char*,能高效地支持一些Redis常用操作,比如append追加、长度计算等。
append时,先判断所需长度(即新追加的字符串长度)。如果所需长度>free,即分配给当前sdshdr的长度不足以容纳整个追加后字符串的长度,就进行扩容,为buf分配两倍于所需总空间。(比如追加后的字符串为"Hello World",所需空间为12,则扩容后的len为11,free为12)
长度计算时,直接获取该sdshdr的len成员(len的值随buf变化而动态更新)。

sds代替char*,还能保证二进制安全。即不妄图以某种特殊格式解析数据,如C中的strlen()依赖于字符串的’/0’来判断字符串的结束。

1.1.2 long

当val是int类型时,redis使用long类型来存储。

斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
使用TOC语法

1.2 list 列表类型

list列表类型有两种实现

1.2.1 ziplist 压缩列表

Ziplist 是由一系列特殊编码的内存块构成的列表,一个ziplist可以包含多个节点(entry),每个节点可以保存一个长度受限的字符数组或整数。
因为ziplist节约内存的特点,因此redis值的5种类型中,有3种(list、hash、zset)都采用了ziplist作为该类型初始化(随着保存的元素的增多,满足一定条件时,转换为其他实现方式)时的底层实现。

1.2.2 linkedlist 双端链表

1.3 hash 哈希类型

hash哈希类型有两种实现

1.3.1 ziplist 压缩列表

Ziplist 是由一系列特殊编码的内存块构成的列表,一个ziplist可以包含多个节点(entry),每个节点可以保存一个长度受限的字符数组或整数。
因为ziplist节约内存的特点,因此redis值的5种类型中,有3种(list、hash、zset)都采用了ziplist作为该类型初始化(随着保存的元素的增多,满足一定条件时,转换为其他实现方式)时的底层实现。

1.3.2 hashtable 字典

1.4 set 集合类型

set集合类型有两种实现

1.4.1 intset 整数集合

intset用于有序、无重复地保存多个整数值。
如果一个set集合1.只保存整数元素2.元素的数量不多,那这个set采用intset作为底层实现

1.4.2 hashtable 字典

1.5 zset 有序集类型

zset有序集类型有两种实现

1.5.1 ziplist 压缩列表

1.5.2 skiplist 跳跃表

2 功能

除了针对单个键值对的操作外, Redis 还提供了一些同时对多个键值对进行处理的功能, 比如事务和 Lua 脚本。
另外, 一些辅助性的功能, 比如慢查询, 以及一些和数据库无关的功能, 比如订阅与发布, 我们也会经常用到。
通过理解这些功能的底层实现, 我们可以更有效地使用它们。
这一部分将对这些功能进行介绍。

2.1 事务

2.2 订阅与发布

2.3 lua 脚本

2.4 排序

2.5 二进制位数组

2.6 慢查询日志

2.7 监视器

3 内部运作机制(单机)

3.1 持久化

redis持久化分为两种,rdb和aof

3.1.1 rdb

rdb将数据库的当前状态(快照snapshot)以磁盘文件的形式保存。在redis重新启动时,可通过载入rdb文件来还愿数据库额度状态
执行rdb的函数:rdbSave()函数
有两条命令可以触发rdbSave:

  1. SAVE命令:会阻塞主进程,直到rdbSave完成。(dbSave期间,服务端不能处理客户端的任何请求)
  2. BGSAVE命令:主进程会fork出一个子进程来做rdbSave,rdbSave完成后,子进程发送信号通知主进程(dbSave期间,服务端正常处理客户端的请求)

SAVE和BGSAVE不可同时执行,也不能多个SAVE或多个BGSAVE同时执行。

redis服务器启动时,通过rdbLoad()函数载入rdb文件。
rdb载入过程中,每载入1000个键,处理1次期间到达的请求,且只处理“发布与订阅”相关的请求。(“发布与订阅”和其他数据库功能完全隔离)

3.1.2 aof

aof(Append Only File)使用redis的网络通讯协议的形式(理解为某种编码),将所有对数据库进行过的写入相关的命令(及其参数)记录到aof文件中,以此记录数据库状态。

aof追加过程:

  1. 将命令传播给aof程序
  2. aof程序将命令转换为网络通讯协议的格式,追加到aof缓存
  3. 保存模式写磁盘(WRITE),将aof缓存中的内容追加到aof文件末尾(SAVE)

aof保存模式:

  1. 不保存:只在redis关闭、aof功能关闭、aof缓存刷新时,进行保存。
  2. 每秒保存一次
  3. 每执行一个命令保存一次

用aof文件还原数据

  1. 创建不带网络连接的伪客户端(fake client)
  2. 读取aof文件,将文本内容转换为(写入)命令
  3. 伪客户端执行命令
  4. 循环2和3,直到aof文件中所有命令被执行

aof文件不断地追加内容,难道让追加不受限制吗?
aof文件重写(rewrite):用一个新aof文件代替旧aof文件,两者保存的数据库状态一致,但新aof文件体积更小。

aof文件重写的原理:新aof仅描述数据的当前(最后)状态(而不记录之前的变化过程)

aof重写放在(后台)子进程里执行

aof重写的过程中,主进程对外服务,若数据库状态发生变化,产生aof开始时和结束时两个时间点数据不一致的问题,如何解决?
主进程将后台aof重写过程中,发生过的写命令,保存在一个aof重写缓存中。等aof重写完成后,再将缓存内容追加到新aof文件末尾

aof重写的触发条件:
手动:BGREWRITEAOF命令
自动:满足以下所有条件

  1. 没有 BGSAVE 命令在进行。
  2. 没有 BGREWRITEAOF 在进行。
  3. 当前 AOF 文件大小大于 server.aof_rewrite_min_size (默认值为 1 MB)。
  4. 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比(默认值为100%)。

3.2 事件

事件是redis服务器的核心,它处理着2项重要的任务

  1. 处理文件事件:在多个客户端中实现多路复用,处理它们发来的命令请求,并将命令的执行结果返回给客户端
  2. 时间事件:实现服务器常规操作(server cron job)

3.2.1 文件事件

文件事件有2种,A_R读事件和A_W写事件。

3.2.1.1 A_R读事件

A_R读事件标志着客户端的命令请求发送状态
当一个客户端和服务端建立连接后,服务端为客户端绑定A_R读事件。直到客户端断开连接,这个读事件才会被移除。此时客户端还没向服务端发送命令,A_R属于等待状态
当客户端给服务端发送命令请求,且该请求已经到达时(相应的socket可以无阻塞地执行读操作)时,该客户端的读事件属于就绪状态
当事件处理器被执行时,就绪的读事件就会被识别,由文件事件分流器分给相应的事件处理器,进行处理。

3.2.1.2 A_W写事件

A_W写事件标志着客户端对命令结果接收状态
和客户端自始至终都关联着的读事件不同,只有在服务端有命令结果传回给客户端时,服务端才会为客户端关联写事件。在命令结果传递结束后,服务端会解除与客户端关联的写事件。

当客户端发送了命令请求,命令请求被接收并执行后,服务端需要将保存在缓存内的命令执行结果返回给客户端,此时服务端为客户端关联写事件。
当服务器有命令结果需要返回给客户端,但客户端的socket还未能执行无阻塞写,那么写事件处于等待状态
当服务器有命令结果需要返回给客户端,并且客户端可以执行无阻塞写,那么写事件处于就绪状态
当命令执行结果被传回给客户端后,客户端和写事件之间的关联被解除(只剩下读事件在关联),至此,返回命令执行结果的动作执行完毕。

值得注意的是,当客户端关联写事件时,此时客户端同时关联了两种文件事件——读事件和写事件——写事件是在客户端建立连接时就关联上的。
由于在同一次文件事件处理器的调用中, 单个客户端只能执行其中一种事件(要么读,要么写,但不能又读又写),所以当客户端同时关联了读事件和写事件,并且读事件和写事件同时处于就绪状态时,事件处理器优先处理读事件。也就是既有命令执行结果要返回,又有新命令请求传来时,服务器优先处理新命令请求。

3.2.2 时间事件

时间事件记录着那些要在指定时间点运行的事件,多个时间事件以无序链表保存在服务器状态中。
每个时间事件主要有3个属性:

  1. when 记录了应该在什么时间点执行事件处理函数,以毫秒格式的 UNIX 时间戳为单位
  2. timeProc 事件处理函数。事件处理函数的是否返回ae.h/AE_NOMORE,决定了该事件是单次执行还是循环执行。单次执行的时间事件会在执行完后从无序链表中被删除;循环执行的时间事件会在执行完后更新when的值为下一次执行的时间点。
  3. next 指针

当时间事件处理器被执行时,会遍历无序链表中的所有节点,检查它们的到达时间(when属性),并执行其中已到达的事件。
正常模式的Redis中只带有serverCron一个时间事件(实际上该函数中可以包括n件事),在 benchmark 模式下, Redis 也只使用两个时间事件。因此无序链表就是一个指针(只有一个时间事件,链表只有1个节点),遍历不会影响时间事件处理器的性能。

serverCron:对于持续运行的服务器来说,服务器需要定期对自身的资源和状态进行必要的检查和整理,从而让服务器维持在一个健康稳定的状态。这类操作被称为常规操作(cron job)
在 Redis 中, 常规操作由 redis.c/serverCron 实现, 它主要执行以下操作:

  1. 更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等。
  2. 清理数据库中的过期键值对。
  3. 对不合理的数据库进行大小调整。
  4. 关闭和清理连接失效的客户端。
  5. 尝试进行 AOF 或 RDB 持久化操作。
  6. 如果服务器是主节点的话,对附属节点进行定期同步。
  7. 如果处于集群模式的话,对集群进行定期同步和连接测试。

redis将serverCron作为时间事件来运行,以保证它会每隔一段时间就自动运行一次。又因为serverCron需要一直被执行,所以它是一个循环时间事件。
Redis2.6中,默认每100ms执行一次serverCron,1s执行10次,可在redis.conf中进行修改。

3.2.3 事件的执行和调度

既然 Redis 里面既有文件事件, 又有时间事件, 那么如何调度这两种事件就成了一个关键问题。
简单地说, Redis 里面的两种事件呈合作关系, 它们之间包含以下三种属性:

  1. 一种事件会等待另一种事件执行完毕之后,才开始执行,事件之间不会出现抢占
  2. 事件处理器处理文件事件(处理命令请求),执行时间事件(调用 serverCron)
  3. 文件事件的等待时间(类 poll 函数的最大阻塞时间),由距离到达时间最短的时间事件决定。

这些属性表明, 实际处理时间事件的时间, 通常会比时间事件所预定的时间要, 至于延迟的时间有多长, 取决于时间事件执行之前, 执行文件事件所消耗的时间。(根据情况, 如果处理文件事件耗费了非常多的时间, serverCron 被推迟到一两秒之后才能执行, 也是有可能的)

3.3 服务端和客户端

3.3.1 初始化服务端

从启动 Redis 服务器, 到服务器可以接受外来客户端的网络连接这段时间, Redis 需要执行一系列初始化操作。

整个初始化过程可以分为以下六个步骤:

  1. 初始化服务器全局状态。
  2. 载入配置文件。
  3. 创建 daemon 进程。
  4. 初始化服务器功能模块。
  5. 载入数据。
  6. 开始事件循环。

3.3.2 客户端连接到服务器

当 redis 服务端初始化完成后,它就准备好接收外来的客户端的连接了。
当一个客户端通过套接字函数 connect 到服务器时,服务器执行以下步骤

  1. 服务器通过文件事件无阻塞地 accept 客户端连接,并返回一个套接字描述符 fd。
  2. 服务器创建一个客户端对应的 redis.h/redisClient 结构实例,并将该实例加到服务端的已连接客户端的链表中。
  3. 服务器在事件处理器为该 fd 关联读文件事件。

此时服务端可以接收客户端发来的命令请求了。
Redis 以多路复用的方式来处理多个客户端, 为了让多个客户端之间独立分开、不互相干扰, 服务器为每个已连接客户端维持一个 redisClient 结构, 从而单独保存该客户端的状态信息。

redisClient 结构主要包含以下信息:

·套接字描述符。
·客户端正在使用的数据库指针和数据库号码。
·客户端的查询缓冲(query buffer)和回复缓存(reply buffer)。
·一个指向命令函数的指针,以及字符串形式的命令、命令参数和命令个数,这些属性会在命令执行时使用。
·客户端状态:记录了客户端是否处于 SLAVE 、 MONITOR 或者事务状态。
·实现事务功能(比如 MULTI 和 WATCH)所需的数据结构。
·实现阻塞功能(比如 BLPOP 和 BRPOPLPUSH)所需的数据结构。
·实现订阅与发布功能(比如 PUBLISH 和 SUBSCRIBE)所需的数据结构。
·统计数据和选项:客户端创建的时间,客户端和服务器最后交互的时间,缓存的大小,等等。

3.3.3 命令的请求、处理和结果返回

当客户端连上服务器之后, 客户端就可以向服务器发送命令请求了。

从客户端发送命令请求, 到命令被服务器处理、并将结果返回客户端, 整个过程有以下步骤:

  1. 客户端通过套接字向服务器传送命令协议数据。
  2. 服务器通过读事件来处理传入数据,并将数据保存在客户端对应 redisClient 结构的查询缓存中。
  3. 根据客户端查询缓存中的内容,程序从命令表中查找相应命令的实现函数。
  4. 程序执行命令的实现函数,修改服务器的全局状态 server 变量,并将命令的执行结果保存到客户端 redisClient 结构的回复缓存中,然后为该客户端的 fd 关联写事件。
  5. 当客户端 fd 的写事件就绪时,将回复缓存中的命令结果传回给客户端。至此,命令执行完毕。

3.4 数据库

本节描述了 Redis 数据库的构造和实现。
除了说明数据库是如何储存数据对象之外,本节还会讨论键的过期信息是如何保存,而 Redis 又是如何删除过期键的。

4 内部运作机制(多机)

4.1 复制

4.1 Sentinel

4.1 集群

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt
居中的图片: Alt

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值