Redis设计与实现之字符串哈希表列表

目录

一、字符串

1、字符串编码

2、编码的选择

二、哈希表

1、字典编码的哈希表

2、压缩列表编码的哈希表

3、编码的选择

4、哈希命令的实现

三、列表

1、 编码的选择

2、 列表命令的实现

3、阻塞的条件

4、 阻塞

5、 阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

6、 先阻塞先服务(FBFS)策略

7、 阻塞因超过最大等待时间而被取消


一、字符串

REDIS_STRING (字符串)是 Redis 使用得最为广泛的数据类型,它除了是 SET 、GET 等命令 的操作对象之外,数据库中的所有键,以及执行命令时提供给 Redis 的参数,都是用这种类型 保存的。

1、字符串编码

字符串类型分别使用 REDIS_ENCODING_INT 和 REDIS_ENCODING_RAW 两种编码:

  • REDIS_ENCODING_INT 使用 long 类型来保存 long 类型值。

  • REDIS_ENCODING_RAW 则使用 sdshdr 结构来保存 sds (也即是 char* )、long long 、 double 和 long double 类型值。

    换句话来说,在 Redis 中,只有能表示为 long 类型的值,才会以整数的形式保存,其他类型 的整数、小数和字符串,都是用 sdshdr 结构来保存。

2、编码的选择

新创建的字符串默认使用 REDIS_ENCODING_RAW 编码,在将字符串作为键或者值保存进数据库时,程序会尝试将字符串转为 REDIS_ENCODING_INT 编码。 3.2.3 字符串命令的实现Redis 的字符串类型命令,基本上是通过包装 sds 数据结构的操作函数来实现的,没有什么需 要说明的地方。

二、哈希表

REDIS_HASH (哈希表) 是 HSET 、 HLEN 等命令的操作对象,它使用REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_HT 两种编码方式:

1、字典编码的哈希表

当哈希表使用字典编码时,程序将哈希表的键(key)保存为字典的键,将哈希表的值(value)保存为字典的值。

哈希表所使用的字典的键和值都是字符串对象。

下图展示了一个包含三个键值对的哈希表:

 

2、压缩列表编码的哈希表

当使用 REDIS_ENCODING_ZIPLIST 编码哈希表时,程序通过将键和值一同推入压缩列表,从而 形成保存哈希表所需的键 -值对结构: 

新添加的 key-value 对会被添加到压缩列表的表尾。 当进行查找/删除或更新操作时,程序先定位到键的位置,然后再通过对键的位置来定位值的位置。

3、编码的选择

创建空白哈希表时,程序默认使用 REDIS_ENCODING_ZIPLIST 编码,当以下任何一个条件被满

足时,程序将编码从切换为 REDIS_ENCODING_HT :
• 哈希表中某个键或某个值的长度大于 server.hash_max_ziplist_value (默认值为 64)。
• 压缩列表中的节点数量大于server.hash_max_ziplist_entries(默认值为512)。

4、哈希命令的实现

哈希类型命令的实现全都是对字典和压缩列表操作函数的包装,以及几个在两种编码之间进行转换的函数,没有特别要讲解的地方。

三、列表

REDIS_LIST (列表) 是 LPUSH 、 LRANGE 等命令的操作对象,它使用

REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_LINKEDLIST 这两种方式编码:

1、 编码的选择

创建新列表时 Redis 默认使用 REDIS_ENCODING_ZIPLIST 编码,当以下任意一个条件被满足时,列表会被转换成 REDIS_ENCODING_LINKEDLIST 编码:
• 试 图 往 列 表 新 添 加 一 个 字 符 串 值, 且 这 个 字 符 串 的 长 度 超 过server.list_max_ziplist_value (默认值为 64 )。
• ziplist 包含的节点超过 server.list_max_ziplist_entries (默认值为 512 )。

2、 列表命令的实现

因为两种底层实现的抽象方式和列表的抽象方式非常接近,所以列表命令几乎就是通过一对一地映射到底层数据结构的操作来实现的。 既然这些映射都非常直观,这里就不做赘述了,在以下的内容中,我们将焦点放在 BLPOP 、BRPOP 和 BRPOPLPUSH 这个几个阻塞命令的实现原理上。

3、阻塞的条件

BLPOP 、BRPOP 和 BRPOPLPUSH 三个命令都可能造成客户端被阻塞,以下将这些命令统 称为列表的阻塞原语。

阻塞原语并不是一定会造成客户端阻塞:

• 只有当这些命令被用于空列表时,它们才会阻塞客户端。

• 如果被处理的列表不为空的话,它们就执行无阻塞版本的 LPOP 、RPOP 或 RPOPL- PUSH 命令。

作为例子,以下流程图展示了 BLPOP 决定是否对客户端进行阻塞过程:

4、 阻塞

 当一个阻塞原语的处理目标为空键时,执行该阻塞原语的客户端就会被阻塞。 阻塞一个客户端需要执行以下步骤:

1. 将客户端的状态设为“正在阻塞”,并记录阻塞这个客户端的各个键,以及阻塞的最长时(timeout)等数据。

2. 将客户端的信息记录到server.db[i]->blocking_keys中(其中i为客户端所使用的数 据库号码)。

3. 继续维持客户端和服务器之间的网络连接,但不再向客户端传送任何信息,造成客户端 阻塞。

步骤 2 是将来解除阻塞的关键,server.db[i]->blocking_keys 是一个字典,字典的键是那 些造成客户端阻塞的键,而字典的值是一个链表,链表里保存了所有因为这个键而被阻塞的客 户端(被同一个键所阻塞的客户端可能不止一个):

在上图展示的 blocking_keys 例子中,client2 、client5 和 client1 三个客户端就正被 key1 阻塞,而其他几个客户端也正在被别的两个 key 阻塞。

当客户端被阻塞之后,脱离阻塞状态有以下三种方法:
1. 被动脱离:有其他客户端为造成阻塞的键推入了新元素。
2. 主动脱离:到达执行阻塞原语时设定的最大阻塞时间。
3. 强制脱离:客户端强制终止和服务器的连接,或者服务器停机。

以下内容将分别介绍被动脱离和主动脱离的实现方式。

5、 阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

通过将新元素推入造成客户端阻塞的某个键中,可以让相应的客户端从阻塞状态中脱离出来(取消阻塞的客户端数量取决于推入元素的数量)。
LPUSH 、RPUSH 和 LINSERT 这三个添加新元素到列表的命令,在底层都由一个

pushGenericCommand 的函数实现,这个函数的运作流程如下图:

当向一个空键推入新元素时,pushGenericCommand 函数执行以下两件事:

1. 检查这个键是否存在于前面提到的 server.db[i]->blocking_keys 字典里,如果是的 话,那么说明有至少一个客户端因为这个 key 而被阻塞,程序会为这个键创建一个 redis.h/readyList 结构,并将它添加到 server.ready_keys 链表中。

2. 将给定的值添加到列表键中。 readyList 结构的定义如下:

readyList 结构的 key 属性指向造成阻塞的键,而 db 则指向该键所在的数据库。

typedef struct readyList { redisDb *db;robj *key;
} readyList;

举个例子,假设某个非阻塞客户端正在使用 0 号数据库,而这个数据库当前的 blocking_keys属性的值如下:

如果这时客户端对该数据库执行 PUSH key3 value ,那么 pushGenericCommand 将创建一个 db 属性指向 0 号数据库、key 属性指向 key3 键对象的 readyList 结构,并将它添加到服务器 server.ready_keys 属性的链表中:

在我们这个例子中,到目前为止,pushGenericCommand 函数完成了以下两件事:

1. 将readyList添加到服务器。
2. 将新元素value添加到键key3。

虽然 key3 已经不再是空键,但到目前为止,被 key3 阻塞的客户端还没有任何一个被解除阻塞状态。

为了做到这一点,Redis 的主进程在执行完 pushGenericCommand 函数之后,会继续调用 handleClientsBlockedOnLists 函数,这个函数执行以下操作:

  1. 如果 server.ready_keys 不为空,那么弹出该链表的表头元素,并取出元素中的 readyList 值。

  2. 根据readyList值所保存的key和db,在server.blocking_keys中查找所有因为key 而被阻塞的客户端(以链表的形式保存)。

  3. 如果key不为空,那么从key中弹出一个元素,并弹出客户端链表的第一个客户端,然 后将被弹出元素返回给被弹出客户端作为阻塞原语的返回值。

  4. 根据readyList结构的属性,删除server.blocking_keys中相应的客户端数据,取消 客户端的阻塞状态。

  5. 继续执行步骤 3 和 4 ,直到 key 没有元素可弹出,或者所有因为 key 而阻塞的客户端都

    取消阻塞为止。

  6. 继续执行步骤 1 ,直到 ready_keys 链表里的所有 readyList 结构都被处理完为止。用一段伪代码描述以上操作可能会更直观一些:

    def handleClientsBlockedOnLists(): # 执行直到 ready_keys 为空while server.ready_keys != NULL: # 弹出链表中的第一个 readyListrl = server.ready_keys.pop_first_node() # 遍历所有因为这个键而被阻塞的客户端for client in all_client_blocking_by_key(rl.key, rl.db):# 只要还有客户端被这个键阻塞,就一直从键中弹出元素# 如果被阻塞客户端执行的是 BLPOP ,那么对键执行 LPOP # 如果执行的是 BRPOP ,那么对键执行 RPOPelement = rl.key.pop_element()if element == NULL:# 键为空,跳出 for 循环# 余下的未解除阻塞的客户端只能等待下次新元素的进入了 breakelse:# 清除客户端的阻塞信息                             server.blocking_keys.remove_blocking_info(client)     # 将元素返回给客户端,脱离阻塞状态 client.reply_list_item(element)

    6、 先阻塞先服务(FBFS)策略

    值得一提的是,当程序添加一个新的被阻塞客户端到 server.blocking_keys 字典的链表中 时,它将该客户端放在链表的最后,而当 handleClientsBlockedOnLists 取消客户端的阻塞 时,它从链表的最前面开始取消阻塞:这个链表形成了一个 FIFO 队列,最先被阻塞的客户端 总值最先脱离阻塞状态,Redis 文档称这种模式为先阻塞先服务(FBFS,first-block-first-serve)。

    举个例子,在下图所示的阻塞状况中,如果客户端对数据库执行 PUSH key3 value ,那么只有 client3 会被取消阻塞,client6 和 client4 仍然阻塞;如果客户端对数据库执行 PUSH key3 value1 value2 ,那么 client3 和 client4 的阻塞都会被取消,而客户端 client6 依然处于 阻塞状态:

7、 阻塞因超过最大等待时间而被取消

前面提到过,当客户端被阻塞时,所有造成它阻塞的键,以及阻塞的最长时限会被记录在客户端里面,并且该客户端的状态会被设置为“正在阻塞” 。

每次 Redis 服务器常规操作函数(server cron job)执行时,程序都会检查所有连接到服务器 的客户端,查看那些处于“正在阻塞”状态的客户端的最大阻塞时限是否已经过期,如果是的话, 就给客户端返回一个空白回复,然后撤销对客户端的阻塞。

可以用一段伪代码来描述这个过程:

def server_cron_job(): # 其他操作 ...# 遍历所有已连接客户端for client in server.all_connected_client:# 如果客户端状态为“正在阻塞”,并且最大阻塞时限已到达 if client.state == BLOCKING and \client.max_blocking_timestamp < current_timestamp(): # 那么给客户端发送空回复, 脱离阻塞状态client.send_empty_reply()# 并清除客户端在服务器上的阻塞信息server.blocking_keys.remove_blocking_info(client) 
# 其他操作 ...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/218926.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSRF(跨站脚本请求)

一、漏洞原理 CSRF&#xff08;Cross-Site Request Forgery&#xff09;是一种网络安全攻击&#xff0c;攻击者通过欺骗用户在不知情的情况下发送请求&#xff0c;从而实现对目标网站的操作。 网站管理员(已经登录网站后台)——黑客构造的恶意服务器(是网站的创建用户请求)——…

Modbus转Profinet网关使用方法

Modbus转Profinet网关&#xff08;XD-MDPN100/200&#xff09;是用于将Modbus协议和Profinet协议进行转换并进行通迅的设备。Modbus转Profinet网关&#xff08;XD-MDPN100/200&#xff09;无论是新项目还是改造项目都可轻松配置完成通迅互联。 正确的安装和配置对于确保设备的正…

mysql的redolog、undo、binlog的作用

概览&#xff1a; MySQL三大日志包括&#xff1a;undolog&#xff0c;redo log&#xff0c;binlog&#xff0c;它们分别有以下作用&#xff1a; undolog&#xff1a;是Innodb存储引擎事务生成的日志。用于事务的回滚和MVCC&#xff0c;保证了事务的原子性。 redo log&#x…

智能优化算法应用:基于供需算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于供需算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于供需算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.供需算法4.实验参数设定5.算法结果6.参考文献7.MA…

KaiwuDB × 国网山东综能 | 分布式储能云边端一体化项目建设

项目背景 济南韩家峪村首个高光伏渗透率台区示范项目因其所处地理位置拥有丰富的光照资源&#xff0c;该区域住户 80% 以上的屋顶都安装了光伏板。仅 2022 年全年&#xff0c;光伏发电总量达到了百万千瓦时。 大量分布式光伏并网&#xff0c;在输出清洁电力的同时&#xff0c…

Leetcode的AC指南 —— 链表:19.删除链表的倒数第N个节点

摘要&#xff1a; Leetcode的AC指南 —— 链表&#xff1a;19.删除链表的倒数第N个节点。题目介绍&#xff1a;给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 文章目录 一、题目二、解析1、滑动窗口/快慢指针&#xff08;傻傻分不清&…

中文编程工具下载,编程工具构件之复选框构件

一、前言 零基础自学编程&#xff0c;中文编程工具下载&#xff0c;中文编程工具构件之扩展系统菜单构件教程 编程系统化教程链接https://jywxz.blog.csdn.net/article/details/134073098?spm1001.2014.3001.5502 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&a…

NNDL 循环神经网络-梯度爆炸实验 [HBU]

目录 6.2.1 梯度打印函数 6.2.2 复现梯度爆炸现象 6.2.3 使用梯度截断解决梯度爆炸问题 【思考题】梯度截断解决梯度爆炸问题的原理是什么&#xff1f; 总结 前言&#xff1a; 造成简单循环网络较难建模长程依赖问题的原因有两个&#xff1a;梯度爆炸和梯度消失。 循环…

网络基础(八):路由器的基本原理及配置

目录 1、路由概述 2、路由器 2.1路由器的工作原理 2.2路由器的转发原理 3、路由表 3.1路由表的概述 3.2路由表的形成 4、静态路由配置过程&#xff08;使用eNSP软件配置&#xff09; 4.1两个静态路由器配置过程 4.2三个静态路由器配置过程 5、默认路由配置过程 5.…

边缘计算系统设计与实践

随着科技的飞速发展&#xff0c;物联网和人工智能两大领域的不断突破&#xff0c;我们看到了一种新型的计算模型——边缘计算的崛起。这种计算模型在处理大规模数据、实现实时响应和降低延迟需求方面&#xff0c;展现出了巨大的潜力。本文将深入探讨边缘计算系统的设计原理和实…

黑马头条--day01.环境搭建

目录 一.前言 二.环境搭建 1.数据库 2.虚拟机搭建 3.1docker更换源 3.docker安装nacos 4.初始化工程 三.全局异常处理 四.登录加密 五.nacos公共配置数据源和mybatis-plus 六.user模块创建 1.配置文件bootstrap.yml 2.日志文件配置logback.xml 3.登录接口 七.统一结果处…

【INTEL(ALTERA)】 quartus SignalTap 逻辑分析器 – Nios® II 插件 无法检测 Nios® II/f 处理器内核

说明 使用 Nios II 插件将 Nios II/f 处理器内核节点添加到 SignalTap 逻辑分析器时&#xff0c;在 英特尔 Quartus Prime Pro Edition 软件 23.3 版中可能会出现此问题。 错误消息&#xff1a; 无法完成“添加带插件的节点”命令&#xff0c;因为在当前设计中找不到所选 IP。…

ubuntu 自动安装 MKL Intel fortran 编译器 ifort 及完美平替

首先据不完全观察&#xff0c;gfortran 与 openblas是 intel fortran 编译器 ifotr和mkl的非常优秀的平替&#xff0c;openblas连函数名都跟mkl一样&#xff0c;加了一个下划线。 1&#xff0c; 概况 https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-too…

dockerfite创建镜像---INMP+wordpress

搭建dockerfile---lnmp 在192.168.10.201 使用 Docker 构建 LNMP 环境并运行 Wordpress 网站平台 [rootdocker1 opt]# mkdir nginx mysql php [rootdocker1 opt]# ls #分别拖入四个包&#xff1a; nginx-1.22.0.tar.gz mysql-boost-5.7.20.tar.gz php-7.1.10.tar.bz2 wor…

python:import 自定义包或者.py文件时出现:ModuleNotFoundError: no module named 的问题解决

问题&#xff1a; 在以下的示例中&#xff0c;wuHanMoviesSprider.py文件&#xff0c;想要import引用指定目录下的Items类时&#xff0c;出现无法识别module模块的问题(from 的引用处报错)。 原因分析&#xff1a; 正常情况下&#xff0c;被引用的包(或目录)中存在一个空文件…

golang反射(reflect)虽爽,但很贵

标准库 reflect 为 Go 语言提供了运行时动态获取对象的类型和值以及动态创建对象的能力。反射可以帮助抽象和简化代码&#xff0c;提高开发效率。 但是使用反射势必会多出大量的操作指令&#xff0c;导致性能下降 案例 字段赋值方式对比 type Student struct {Name string…

QML 自定义进度条组件开发

一、效果预览 二、介绍&#xff1a; 自定义的QML 屏幕亮度拖动进度条组件CusProgressBar 可跟鼠标移动 更改进度条样式 三、代码 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Material 2.12/***author:Zwj*csdn:来份煎蛋吧*date:2023/12/16*…

爬虫工作量由小到大的思维转变---<第十一章 Scrapy之sqlalchemy模版和改造(番外)>

前言: 正常的pymysql当然问题不大,但是我个人还是建议:sqlalchemy! 因为他更能让我们把精力放在表单设计上,而不执着于代码本身了. (-----版权所有。未经作者书面同意&#xff0c;不得转载或用于任何商业用途!----) 正文: 先提供一个基础模版: 表图: 创建表的sql: CREA…

2023-12-14 二叉树的最大深度和二叉树的最小深度以及完全二叉树的节点个数

二叉树的最大深度和二叉树的最小深度以及完全二叉树的节点个数 104. 二叉树的最大深度 思想&#xff1a;可以使用迭代法或者递归&#xff01;使用递归更好&#xff0c;帮助理解递归思路&#xff01;明确递归三部曲–①确定参数以及返回参数 ②递归结束条件 ③单层逻辑是怎么样…

C#中的封装、继承和多态

1.引言 在面向对象的编程中&#xff0c;封装、继承和多态是三个重要的概念。它们是C#语言中的基本特性&#xff0c;用于设计和实现具有高内聚和低耦合的代码。本文将详细介绍C#中的封装、继承和多态的相关知识。 目录 1.引言2. 封装2.1 类2.2 访问修饰符 3. 继承4. 多态4.1 虚方…