Redis中key-value实现

2023-11-07

实现字典的方法有很多种:

  • 最简单的就是使用链表或数组, 但是这种方式只适用于元素个数不多的情况下;
  • 要兼顾高效和简单性,可以使用哈希表;
  • 如果追求更为稳定的性能特征, 并且希望高效地实现排序操作的话, 则可以使用更为复杂的平衡树;

在众多可能的实现中, Redis 选择了高效且实现简单的哈希表作为字典的底层实现。

dict 类型的 API , 它们的作用及相应的算法复杂度:

操作类型 操作 函数 算法复杂度
创建 创建一个新字典 dictAdd O(1)
添加或更新给定键的值 dictFind O(1)
在字典中查找给定键的值 dictGetRandomKey O(N)
删除 根据给定键,删除字典中的键值对 dictRelease O(N)
清空并重置(但不释放)字典 dictResize O(N)
扩大字典 dictRehash O(N)
在给定毫秒内,对字典进行rehash dict 类型使用了两个指针分别指向两个哈希表。

其中, 0 号哈希表(ht[1])则只有在程序对 0 号哈希表进行 rehash 时才使用。

接下来两个小节将对哈希表的实现,以及哈希表所使用的哈希算法进行介绍。

哈希表实现

字典所使用的哈希表实现由 table 属性是一个数组, 数组的每个元素都是一个指向 dictEntry 都保存着一个键值对, 以及一个指向另一个 next 属性指向另一个 dictEntry 可以通过 dictht dictht 和数个 dict 类型,那么整个字典结构可以表示如下:

在上图的字典示例中, 字典虽然创建了两个哈希表, 但正在使用的只有 0 号哈希表, 这说明字典未进行 rehash 状态。

哈希算法

Redis 目前使用两种不同的哈希算法:

  1. http://code.google.com/p/smhasher/ 。
  2. 基于 djb 算法实现的一个大小写无关散列算法:具体信息请参考 dictCreate 函数创建并返回一个新字典:

dict  * =  dictCreate( & hash_type, NULL);
table 属性分配任何空间:
  • ht[1]->table 的空间分配将在 rehash 开始时进行;

添加键值对到字典

根据字典所处的状态, 将一个给定的键值对添加到字典可能会引起一系列复杂的操作:

  • 如果字典为未初始化(也即是字典的 0 号哈希表的 
  • 字典为空;
  • 添加新键值对时发生碰撞处理;
  • 添加新键值对时触发了 rehash 操作;

添加新元素到空白字典

当第一次往空字典里添加键值对时, 程序会根据 d->ht[0]->table 分配空间 (在目前的版本中, 4 )。

以下是字典空白时的样子:


以下是往空白字典添加了第一个键值对之后的样子:

添加新键值对时发生碰撞处理

在哈希表实现中, 当两个不同的键拥有相同的哈希值时, 我们称这两个键发生碰撞(collision), 而哈希表实现必须想办法对碰撞进行处理。

字典哈希表所使用的碰撞解决方法被称之为key4 和 key4 的哈希值和 0 号索引上发生碰撞。

通过将 key1-value1 两个键值对用链表连接起来, 就可以解决碰撞的问题:


添加新键值对时触发了 rehash 操作

对于使用链地址法来解决碰撞问题的哈希表 size属性)和它所保存的节点的数量(ht[0])进行 rehash 操作: 在不修改任何键值对的情况下,对哈希表进行扩容, 尽量将比率维持在 1:1 左右。

ht[0] 进行检查, 对于 size 和 ratio =used / size 满足以下任何一个条件的话,rehash 过程就会被激活:

  1. ratio >= 1 ,且变量  ratio 大于变量  dict_force_resize_ratio 的值为 

    什么时候 BGSAVE 或 copy on write 机制, 程序会会暂时将 dict_can_resize 会重新被设为真。

    另一方面, 当字典满足了强制 rehash 的条件时, 即使 BGSAVE 或 

  2. 创建一个比 ht[1]->table ;
  3. 将 ht[1]->table ;
  4. 将原有 ht[1] 替换为新的 
  5. 设置字典的 0 ,标识着 rehash 的开始;
  6. 为 ht[0]->used 的两倍;

这时的字典是这个样子:


2. Rehash 进行中

在这个阶段, ht[1]->table , 因为 rehash 是分多次进行的(细节在下一节解释), 字典的 ht[0] 的哪个索引位置上。

以下是 2 时,字典的样子:


注意除了节点的移动外, 字典的 ht[0]->used 和 ht[0] 迁移到 

  • 释放 ht[1] 来代替 ht[1] 成为新的 ht[1] ;
  • 将字典的 -1 ,标识 rehash 已停止;

    以下是字典 rehash 完毕之后的样子:


    对比字典 rehash 之前和 rehash 之后, 新的 _dictRehashStep 和 _dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动 rehash ;

  • _dictRehashStep , ht[1]->table 。

    在 rehash 开始进行之后(-1), 每次执行一次添加、查找、删除操作, dictRehashMilliseconds 可以在指定的毫秒数内, 对字典进行 rehash 。

    当 Redis 的服务器常规任务执行时, ht[0] 上进行,还需要在 ht[1] 而不是 ht[0] 的节点数量在整个 rehash 过程中都只减不增。

字典的收缩

上面关于 rehash 的章节描述了通过 rehash 对字典进行扩展(expand)的情况, 如果哈希表的可用节点数比已用节点数大很多的话, 那么也可以通过对哈希表进行 rehash 来收缩(shrink)字典。

收缩 rehash 和上面展示的扩展 rehash 的操作几乎一样,它执行以下步骤:

  1. ht[0]->table 小的  ht[0]->table 中的所有键值对迁移到  ht[0] 的数据清空,并将  ht[0] ;

扩展 rehash 和收缩 rehash 执行完全相同的过程, 一个 rehash 是扩展还是收缩字典, 关键在于新分配的 ht[1]->table 比 ht[1]->table 比 数据库》一章的《迭代器实现 —— 对字典进行迭代实际上就是对字典所使用的哈希表进行迭代:

  • 迭代器首先迭代字典的第一个哈希表, 然后,如果 rehash 正在进行的话, 就继续对第二个哈希表进行迭代。
  • 当迭代哈希表时, 找到第一个不为空的索引, 然后迭代这个索引上的所有节点。
  • 当这个索引迭代完了, 继续查找下一个不为空的索引, 如此循环, 一直到整个哈希表都迭代完为止。

整个迭代过程可以用伪代码表示如下:

def iter_dict(dict):

    # 迭代 
0  号哈希表
    iter_table(ht[
0 ] -> table)

    # 如果正在执行 rehash ,那么也迭代 
1  号哈希表
    
if  dict.is_rehashing(): iter_table(ht[ 1 ] -> table)


def iter_table(table):

    # 遍历哈希表上的所有索引
    
for  index  in  table:

        # 跳过空索引
        
if  table[index].empty():
            
continue

        # 遍历索引上的所有节点
        
for  node  in  table[index]:

            # 处理节点
            do_something_with(node)

字典的迭代器有两种:

  • 安全迭代器:在迭代进行过程中,可以对字典进行修改。
  • 不安全迭代器: 在迭代进行过程中,不对字典进行修改。

以下是迭代器的数据结构定义:

/*
 * 字典迭代器
 
*/
typedef 
struct  dictIterator {

    dict 
* d;                 //  正在迭代的字典

    
int  table,               //  正在迭代的哈希表的号码(0 或者 1)
        index,               //  正在迭代的哈希表数组的索引
        safe;                //  是否安全?

    dictEntry 
* entry,        //  当前哈希节点
               * nextEntry;    //  当前哈希节点的后继节点

} dictIterator;

以下函数是这个迭代器的 API ,它们的作用及相关算法复杂度:

函数 作用 算法复杂度
dictGetSafeIterator 创建一个安全迭代器。 O(1)
NULL 。 O(1)
<tt literal"="" style="background-color: transparent; color: rgb(34, 34, 34); font-size: 1.1em;">dictReleaseIterator 释放迭代器。 O(1)

小结

  • 字典由键值对构成的抽象数据结构。
  • Redis 中的数据库和哈希键都基于字典来实现。
  • Redis 字典的底层实现为哈希表,每个字典使用两个哈希表,一般情况下只使用 0 号哈希表,只有在 rehash 进行时,才会同时使用 0 号和 1 号哈希表。
  • 哈希表使用链地址法来解决键冲突的问题。
  • Rehash 可以用于扩展或收缩哈希表。
  • 对哈希表的 rehash 是分多次、渐进式地进行的。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis中key-value实现 的相关文章

  • 获取列位置

    在 Cassandra DB 中 使用有序列族 我知道你能得到切片 但你能得到位置吗 例如 在此数据模型中 我保存如下分数 Scores 1000 bob lucas 900 tim 800 mario 知道用户的分数为 900 并且他的昵
  • CouchDB“加入”两个文档

    我有两个看起来有点像这样的文档 Doc id AAA creator id data DataKey id credits left 500 times used 0 data id AAA 我想要做的是创建一个视图 它允许我传递 Data
  • 是否有一个 nosql 存储也允许存储实体之间的关系?

    我正在寻找 nosql 键值存储 它还提供存储 维护存储实体之间的关系 我知道 Google App Engine 的数据存储允许实体之间拥有和不拥有的关系 任何流行的 nosql 商店都提供类似的东西吗 尽管它们中的大多数都是无模式的 但
  • dynamoDB 如何存储数据?

    由于Dynamodb以键值对的形式存储数据 其中键是主键的类型 值是与其关联的数据 我想知道dynamo db是否真正理解值 json 我所说的值是指json与键关联的对象 RDBMS 中的一行 dynamo db 是否理解有一些属性以及它
  • Neo4j 入门

    我对 neo4j 完全陌生 很抱歉问这样一个基本问题 我已经安装了 neo4j 我正在使用 shell localhost 7474 webadmin console 我正在寻找一个很好的示例 它使用一些 shell 命令从预先存在的图形数
  • Apache Cassandra 如何进行聚合操作?

    总的来说 我对 Apache Cassandra 和 nosql 相当陌生 在 SQL 中 我可以执行聚合操作 例如 SELECT country sum age count AS averageAge FROM people GROUP
  • Cassandra 集群 - 特定节点 - 特定表高丢弃突变

    我在生产中的压缩策略是 LZ4 压缩 但我将其修改为 Deflate 对于压缩更改 我们必须使用 nodetool Upgradesstables 强制升级所有 sstable 上的压缩策略 但是 一旦在集群中的所有 5 个节点上完成了 U
  • 是否有 NoSQL 解决方案的比较(在某些情况下哪个更好?)[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 当我在 Linux PHP 架构中构建基于密钥的归档应用程序时 我正在尝试了解有关 NoSQL 的更多信息 谁能解释一下主要解决方案
  • redis - 使用哈希

    我正在使用 redis 为我的 Web 应用程序实现社交流和通知系统 我是 redis 的新手 我对哈希值及其效率有一些疑问 我读过这篇很棒的文章Instagram 帖子 http instagram engineering tumblr
  • 当我们有多对多关系时,如何在 firebase 中获取数据

    我读了这个问题Firebase 中的多对多关系 https stackoverflow com questions 41527058 many to many relationship in firebase 这里描述了如何在 fireba
  • 一次更新猫鼬中的多个文档

    我有一个用户文档数组 每个用户都有关注者属性 它是一个数字 我只想将此属性增加 1 然后立即更新数据库中的所有这些用户文档 更多细节 在请求中 我有一组用户 id 我使用这些 id 进行查询以获取一组用户文档 const users awa
  • mongodb 正在运行吗?

    我已经在我的 Unix 服务器上安装了 Mongodb 和 PHP 驱动程序 我的问题是如何判断 Mongodb 是否正在运行 是否有一个简单的命令行查询来检查状态 如果我从外壳程序启动一次 如果我退出外壳程序 它会继续运行 情况似乎并非如
  • CAP 定理 - 可用性和分区容错性

    当我尝试理解CAP中的 可用性 A 和 分区容错性 P 时 我发现很难理解各种文章的解释 我感觉A和P可以在一起 我知道事实并非如此 这就是为什么我无法理解 简单解释一下 A和P是什么以及它们之间的区别 一致性意味着整个集群中的数据是相同的
  • 何时使用 NoSql,使用哪一种? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • redis能完全取代mysql吗?

    简单的问题 我是否可以使用 redis 而不是 mysql 来处理各种 Web 应用程序 社交网络 地理位置服务等 IT 领域没有什么是不可能的 但有些事情可能会变得极其复杂 将键值存储用于全文搜索之类的事情可能会非常痛苦 另外 据我所知
  • MongoDB:如何使用单个命令更新多个文档?

    我惊讶地发现以下示例代码仅更新单个文档 gt db test save id 1 foo bar gt db test save id 2 foo bar gt db test update foo bar set test success
  • 在 MongoDB 中查找 7 天前的记录

    我有一个包含对象的集合 如下所示 1 id ObjectId 551c6605e4c6ac495c923aab sender id ObjectId 551c6605e4c6ac495c923aac rep sender id 38 sen
  • 非关系数据库设计[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有兴趣了解您使用过的设计策略非关系型 nosql 数据库 也就是说 不使用传统关系设计或 SQL 的 大多数是新的 数据存储类 例如
  • 什么时候不应该使用 Cassandra? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 相关话题已经有很多讨论了卡桑德拉 http cassandra apache org lately Twitter Digg Facebook
  • 有没有多核利用NoSQL系统?

    我从昨天开始就开始使用 MongoDB 并且非常喜欢它 我正在尝试导入大量数据 20 亿行 并为其建立索引 但它似乎没有使用我的系统拥有的 8 个核心 并且导入以正常速率 60000 条记录 秒 进行 我只能想象索引这个集合中的两列可能需要

随机推荐

  • row_number() over partition by 分组聚合

    row number over partition by 分组聚合 分组聚合 就是先分组再排序 可以的话顺手标个排名 如果不想分组也可以排名 如果不想分组同时再去重排名也可以 ROW NUMBER OVER PARTITION BY col
  • Vue获得指定id的html,vue.js怎么删除指定id的数据

    本文环境 windows7 vue2 9 6 该方法适用于所有品牌的电脑 vue js删除指定id数据的方法 注意click需要传入当前的id值deletes function id this http delete http jsonpl
  • 认识sass

    一 认识sass SASS Syntactically Awesome Stylesheet 是一个CSS预处理器 有助于减少CSS的重复 节省时间 它是更稳定和强大的CSS扩展语言 描述文档的样式干净和结构 扩展了 CSS3 增加了规则
  • Java面向对象基础

    面向对象 学习内容 l 面向对象思想 l 类与对象及其使用 l 对象的内存图 l 成员变量和局部变量的区别 l 匿名对象 l 封装 private l this关键字 l 构造方法 l static关键字 l 继承 l 多态 l 抽象类 l
  • AVR单片机最小系统 基本硬件线路与分析

    AVR单片机最小系统 基本硬件线路与分析 AVR仿真器 AVR编程器 二合一 AVR JTAG与ISP 二合一V2 5 经典推荐 298 00元 富士通 MB90092 DEMO OSD视频字符叠加开发板 380 00元 国产 AVR JT
  • Wireshark使用技巧

    前言 Wireshark是一款图形界面的网络嗅探器 支持多种平台 是网络流量分析的利器 它的创始人是Gerald Combs 前身是Ethereal 作为开源项目经过众多开发者的完善它已经成为使用量最大的安全工具之一 最近刚把 Wiresh
  • 翰文进度计划软件横道图不显示文字_斑马进度计划2019,编制进度计划仅需8步!请收藏...

    斑马进度计划2019新功能简介 大突破 从此双代号网络计划支持父子结构啦 计划分分钟逐级拆解细化 前锋线也能自下而上反馈进度 进行多级管控里程碑预警啦 还有大家最心心念的TPM 全面计划管理 设计 招采 施工以及各专业全面联动计算 从此 项
  • 使用Crash工具分析 Linux dump文件

    前言 Linux 内核 以下简称内核 是一个不与特定进程相关的功能集合 内核的代码很难轻易的在调试器中执行和跟踪 开发者认为 内核如果发生了错误 就不应该继续运 行 因此内核发生错误时 它的行为通常被设定为系统崩溃 机器重启 基于动态存储器
  • 软件测试行业就业前景到底怎么样?

    软件测试就业前景非常好 目前IT行业对于软件测试方面的人才需求是非常大的 软件产品的质量对于一个软件来说是攸关生死的 各企业越来越重视软件产品质量 而软件测试的工作就是让软件质量越来越好 还有就是软件测试的工资待遇是非常好的 和其它职业相比
  • 《数据库系统概论》课程学习(26)——习题集(第1-14章)含答案

    数据库系统概论习题集 第一章 绪论 一 选择题 1 DBS是采用了数据库技术的计算机系统 DBS是一个集合体 包含数据库 计算机硬件 软件和 A 系统分析员 B 程序员 C 数据库管理员 D 操作员 2 数据库 DB 数据库系统 DBS 和
  • ssm框架整合(项目步骤)

    目录 一 前言 二 SSM框架 2 1 SSM整合到底整合什么 2 2 为什么要整合到一起 2 3 由谁来整合 2 4 ResponseBody注解的作用是什么 2 5 JSON 三 各框架应用场景 3 1 SpringMVC框架 3 2
  • 建立单链表并交换表中任意两个元素

    功能 建立单链表并交换表中任意两个元素 time 2017年3月12日15 07 25 include
  • 2021年 IEEE VIS 科学可视化与体渲染论文整理与分析

    因为最近工作的关系 需要研究一下IEEE VIS中2017年以后的与我之前主要方向 体渲染 医学可视化 有关的论文 我把这些年全部的论文进行了筛选和梳理 总共筛选出57篇论文 打算写一个文章来记录这些内容 这个栏目是2021年的九篇论文的介
  • MNIST数据库介绍及转换

    MNIST数据库介绍 MNIST是一个手写数字数据库 它有60000个训练样本集和10000个测试样本集 它是NIST数据库的一个子集 MNIST数据库官方网址为 http yann lecun com exdb mnist 也可以在win
  • springMVC ResponseBody 返回汉字乱码解决方案

    本文查考借鉴 http blog yimik com archives 899 js里通过ajax调用springmvc 后台返回的中文字符串乱码 通过搜索找解决方 大都让配置StringHttpMessageConverter这个bean
  • 本地JAR打镜像,并启动

    1 准备好jar 和Dockerfile文件 2 使用命令打镜像 docker build t wstest 3 查看镜像 4 由于服务是两个端口 使用以下命令 5 优化怎么随着docker的开启而启动 docker run restart
  • Maltrail恶意流量检测系统

    Maltrail恶意流量检测系统 项目介绍 项目GitHub地址 项目架构 项目数据集 运行方式 订阅源扩展 数据采集模块提取 项目介绍 maltrail是一款轻量级的恶意流量检测系统 其工作原理是通过采集网络中各个开源黑样本 包括IP 域
  • python操作sql

    from pymysql import connect def main 创建connection连接 conn connect host localhost port 3306 user root password 123456 data
  • 深入浅出UML类图(一)

    在UML 2 0的13种图形中 类图是使用频率最高的UML图之一 Martin Fowler在其著作 UML Distilled A Brief Guide to the Standard Object Modeling Language
  • Redis中key-value实现

    实现字典的方法有很多种 最简单的就是使用链表或数组 但是这种方式只适用于元素个数不多的情况下 要兼顾高效和简单性 可以使用哈希表 如果追求更为稳定的性能特征 并且希望高效地实现排序操作的话 则可以使用更为复杂的平衡树 在众多可能的实现中 R