redis到底是怎么样进行渐进式hash的

news2024/12/1 0:38:19

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。那么redis的底层是如何来存储数据的呢?

一、redis如何在存储大量的key时候,查询速度还能接近O(1)呢?

查询速度接近O(1)的数据结构通常让我们想到的就是HashMap结构,那下面我从源码来追踪下redis到底是不是使用的HashMap结构呢?生成的全局hashTable的大小为4
redis的数据最外层的结构是redisDb(server.h文件) ,其定义如下:

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

从上面定义我们可以看出redisDb 的保存数据的结构是dict(dict.h),那么我们从文件中获取

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

dict 包含了两个hash表(dictht ht[2]),这里使用两个hash表就是为了后续给渐进式rehash来进行服务的.属性rehashidx == -1时候代表不是处于reshaing中。
dictht 就一个hashtable,其包含dictEntry 的数组。然后我们继续看下

   typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

dictEntry 的就是hash表中的一个键值对,那么根据上面的代码我们可以绘出redis中内存结构图。
在这里插入图片描述

redis的rehash过程怎么处理呢?

随着redis中key的数据量增多,随着key的增多,那么dictEntry 连越来越长,这个时候查询出来的性能将会越来越慢。这个时候就需要对hashTable进行扩容,在数据量大的时候如果等到所有的扩容完成,那么必然会导致redis长时间等待,那么这个时候我们就采用渐进式rehash方式来进行扩容。

什么是渐进式rehash呢?

Redis 默认使用了两个全局哈希表:dictht[0]和哈希表 dictht[1],一开始,当你刚插入数据时,默认使用dictht[0],此时的dictht[1] 并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步:

1、给dictht[1]分配更大的空间,一般是当前dictht[0]已使用大小的2倍,但是必须满足是2的整数倍!
2、把哈希表0 中的数据重新映射并拷贝到哈希表1 中(在hash表1下进行重新计算hash值);
3、释放哈希表 0 的空间
4、把dictht[0]指向刚刚创建好的dictht[1]

什么时候进行hash

  • 1、在没有fork子进程进行RDS或者AOF数据备份的时候且ht[0] .used >= ht[0].size时
  • 2、 在有fork子进程进行RDS或者AOF数据备份的时候且ht[0] .used > ht[0].size * 5时
    扩容,肯定是在添加数据的时候才会扩容,所以我们找一个添加数据的入口,我们从源码层面进行下验证:
int dictReplace(dict *d, void *key, void *val)
{
    dictEntry *entry, *existing, auxentry;

    /* Try to add the element. If the key
     * does not exists dictAdd will succeed. */
    entry = dictAddRaw(d,key,&existing);
    if (entry) {
        dictSetVal(d, entry, val);
        return 1;
    }

    /* Set the new value and free the old one. Note that it is important
     * to do that in this order, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */
    auxentry = *existing;
    dictSetVal(d, existing, val);
    dictFreeVal(d, &auxentry);
    return 0;
}

然后继续查看dictAddRaw方法

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;

    if (dictIsRehashing(d)) _dictRehashStep(d);

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. */
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;
}

然后继续往下看_dictKeyIndex方法

static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    unsigned long idx, table;
    dictEntry *he;
    if (existing) *existing = NULL;

    /* Expand the hash table if needed */
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
    for (table = 0; table <= 1; table++) {
        idx = hash & d->ht[table].sizemask;
        /* Search if this slot does not already contain the given key */
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                if (existing) *existing = he;
                return -1;
            }
            he = he->next;
        }
        if (!dictIsRehashing(d)) break;
    }
    return idx;
}

从上面代码注释可以看出来,_dictExpandIfNeeded就是用来进行扩容的

   /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (!dictTypeExpandAllowed(d))
        return DICT_OK;
    if ((dict_can_resize == DICT_RESIZE_ENABLE &&
         d->ht[0].used >= d->ht[0].size) ||
        (dict_can_resize != DICT_RESIZE_FORBID &&
         d->ht[0].used / d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used + 1);
    }
    return DICT_OK;
}
  • 1、在hashtable扩容的时候,如果正在扩容的时将不会出发扩容操作
  • 2、DICT_HT_INITIAL_SIZE的大小为4,即默认创建的hashtable大小为4
  • 3、dict_force_resize_ratio的值为5
    *这里需要关注dict_can_resize 这个字段什么时候被赋值了,

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式事务方案学习

第100篇文章啦&#xff01;分布式事务在面试中分布式事务也是十分重要的点&#xff0c;所以学习完分布式锁后我们就来学习分布式事务吧。 事务表示的是我们在业务逻辑中对数据库进行操作的一组单元&#xff0c;需要同时成功或同时失败&#xff0c;不了解的小伙伴们可以看一下下…

Linux编译器-gcc/g++(动静态链接)

目录 一、Linux编译器-gcc/g的使用1.1 背景知识1.2 预处理&#xff0c;编译&#xff0c;汇编&#xff0c;链接1.3 动静态链接 二、补充sudo设置 一、Linux编译器-gcc/g的使用 1.1 背景知识 我们为什么能在windows或者linux下进行C/C或者其它形式的开发呢&#xff1f;前提条件…

我在VScode学Java(Java一维数组、二维数组、JVM中的堆和栈)重制版

​ 我的个人博客主页&#xff1a;如果’真能转义1️⃣说1️⃣的博客主页 关于Java基本语法学习---->可以参考我的这篇博客&#xff1a;《我在VScode学Java》 Java一维数组、二维数组 零._.在Java中_什么是数组Java 数组是一种数据结构&#xff0c;存储一组相同类型的数据。引…

Docker网络模型(七)使用 IPvlan 网络

使用 IPvlan 网络 IPvlan 驱动为用户提供了全面控制 IPv4 和 IPv6 寻址的能力。 IPvlan 让操作者能完全操控二层&#xff08;数据链路层&#xff09;网络的 vlan 标签&#xff0c;甚至也提供了三层&#xff08;网络传输层&#xff09;路由控制给感兴趣的用户。对于抽象出物理限…

【SpringSecurity】CSRF、环境配置、授权、认证功能、记住我功能实现

SpringSecurity 文章目录 SpringSecurityCSRF跨站请求伪造攻击开发环境搭建认证直接认证使用数据库认证自定义登录界面 授权基于角色的授权基于权限的授权使用注解判断权限 记住我SecurityContext SpringSecurity是一个基于Spring开发的非常强大的权限验证框架&#xff0c;其核…

Java快速安装以及入门指南

安装 Java 环境教程 Java 是一种广泛应用于软件开发、Web 应用程序和移动应用程序等领域的编程语言。如果您要使用 Java 进行开发或运行需要 Java 程序&#xff0c;您需要先在计算机上安装 Java 环境。 本教程将向您介绍如何在 Windows 操作系统上安装和验证 Java 环境。还将…

第2章 可行性研究

文章目录 第2章 可行性研究2.1 可行性研究的任务2.2 可行性研究过程2.3 系统流程图2.3.1符号2.3.3 分层 2.4 数据流图2.4.1 符号1. 数据源点或终点2. 数据加工&#xff08;变换数据的处理&#xff09;3. 数据存储4.数据流数据流与数据加工之间的关系 2.4.2绘制数据流图的例子顶…

chatgpt赋能python:Python3.6.5到Python3.7.5:升级指南

Python 3.6.5到Python 3.7.5&#xff1a;升级指南 Python是一种广泛使用的编程语言&#xff0c;拥有强大的库和框架&#xff0c;能够开发各种类型的应用程序。在Python的发行版中&#xff0c;版本更新是常见的过程&#xff0c;以提供更好的性能和新的功能。 本文将介绍如何将…

手记系列之六 ----- 分享个人使用kafka经验

前言 本篇文章主要介绍的关于本人从刚工作到现在使用kafka的经验&#xff0c;内容非常多&#xff0c;包含了kafka的常用命令&#xff0c;在生产环境中遇到的一些场景处理&#xff0c;kafka的一些web工具推荐等等。由于kafka这块的记录以及经验是从我刚开始使用kafka&#xff0…

chatgpt赋能python:Python怎么一行一行读文本?

Python怎么一行一行读文本&#xff1f; 在Python中&#xff0c;要一行一行地读取文本文件&#xff0c;我们可以使用Python内置函数readline()。它每次读取一行文本&#xff0c;并且会自动把文本的换行符\n去掉。下面我们来详细了解如何使用readline()函数读取文本文件。 读取…

4.卡尔曼滤波原理及实战

欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 文章目录 0.关于卡尔曼1.卡尔曼滤波算法2.卡尔曼滤波算法的应用一个简单例子一个复杂的例子参考 0.关于卡尔曼 卡尔曼&#xff0c;匈牙利数学家&#xff0c;1930年出生于匈牙利首都布达佩斯…

chatgpt赋能python:Python数据分析:Vlookup函数在Python中的实现

Python数据分析&#xff1a;Vlookup函数在Python中的实现 简介 Vlookup是一种常见的数据分析函数&#xff0c;用于在两个数据表/数据集中查找并关联相应的数据。这个函数是在Excel中非常常见的&#xff0c;但是当我们进行大型数据分析时&#xff0c;我们可以使用Python来实现…

2023.6.8小记——嵌入式系统初识、什么是ARM架构?

今天还挺充实的&#xff0c;早上在图书馆本来想学一下notion&#xff0c;结果看李沐老师的动手深度学习看到十点半&#xff0c;在电脑上配置了李沐老师的d2l和jupyter&#xff0c;等后续有时间的时候再继续学。 下午看了一下notion的使用方法&#xff0c;这玩意初学者用起来是…

chatgpt赋能python:Python如何一行输出五个星号

Python如何一行输出五个星号 Python是一种流行的编程语言&#xff0c;它被广泛用于各种领域&#xff0c;包括Web开发&#xff0c;数据科学和机器学习。在这篇文章中&#xff0c;我们将探讨如何使用Python在一行中输出五个星号。 输出五个星号的方法 Python提供了几种方法来在…

牛客网语法刷题(C语言) — 输出格式化

&#x1f929;本文作者&#xff1a;大家好&#xff0c;我是paperjie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 &#x1f970;内容专栏&#xff1a;这里是《C语言—语法篇》专栏&#xff0c;笔者用重金(时间和精力)打造&#xff0c;基础知识一网打尽&#xff0c;…

从字符串中读写的方法:c语言中的sscanf、sprintf函数,c++中的I/O流strstream、stringstream

一、sscanf、sprintf函数 众所周知&#xff0c;c语言中我们常使用的标准输入输出流函数为scanf()、printf()&#xff0c;默认从键盘上输入数据、向屏幕输出数据。此外&#xff0c;c语言中还提供了另一组库函数sscanf()、sprintf()函数&#xff0c;它们的功能与前者相同&#x…

spark复习重点

什么是大数据 大数据是指数据量巨大、类型繁多、处理速度快的数据集合。它不仅包括传统的结构化数据&#xff0c;还包括非结构化数据、半结构化数据等多种形式的数据。大数据的处理需要使用特殊的技术和工具&#xff0c;如分布式存储、分布式计算、数据挖掘、机器学习等。 大…

软考A计划-系统架构师-官方考试指定教程-(7/15)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

uniapp主题切换功能的第二种实现方式(scss变量+require)

在上一篇 “uniapp主题切换功能的第一种实现方式&#xff08;scss变量vuex&#xff09;” 中介绍了第一种如何切换主题&#xff0c;但我们总结出一些不好的地方&#xff0c;例如扩展性不强&#xff0c;维护起来也困难等等&#xff0c;那么接下我再给大家介绍另外一种切换主题的…

聊聊那些奇葩的代码规范 —— 滥用静态导入

因为有些要求感觉实是太过奇葩&#xff0c;收集下来娱乐下大家。 代码规范要求 要求如果代码可以静态导入的话&#xff0c;就必须要静态导入。 所有的代码如果不静态导入&#xff0c;就直接 PR 拒绝合并。 举例&#xff1a;equalsAnyIgnoreCase("test","tes…