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

news2024/11/30 10:50:05

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/625866.html

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

相关文章

JavaSE笔记(六)重制版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqvdx4nQ-1685927311485)(https://s2.loli.net/2022/10/04/SkAn9RQpqC4tVW5.png)] 集合类与IO 前面我们已经把基础介绍完了&#xff0c;从这节课开始&#xff0c;我们就正式进入到集合类的讲解中。 …

【ChatGPT+Python】Landsat卫星图像黑边去云及旋转校正

引言 下图是一张Landsat图像的示例&#xff08;右图&#xff09;。我们可以明显地看到四周的黑边和倾斜的角度。这是由于卫星传感器成像导致的。一般情况下&#xff0c;我们是不需要去除黑边和选择的&#xff0c;因为这样做之后投影信息和位置信息就不正确了。但对于做深度学习…

React 基本介绍

目录 1、React是什么 2、React 三大颠覆性的特点 2.1 组件 2.2 JSX 2.3 Virtual DOM 3、Flux 架构&#xff08;redux&#xff09; 3.1 Flux 3.2 redux 4、打包工具&#xff08;webpack&#xff09; 4.1 webpack与RequireJS、browserify 4.2 模块规范 4.3 非 JavaSc…

二叉树的数学性质、最大堆的实现

每层个数的通式 第一层&#xff1a; 2 0 第二层&#xff1a; 2 1 第三层&#xff1a; 2 2 第四层&#xff1a; 2 3 每层个数的通式 2 n − 1 个&#xff0c; n 为层数 \begin{aligned} 第一层&#xff1a;2^0 \\ 第二层&#xff1a;2^1 \\ 第三层&#xff1a;2^2 \\ 第四层&…

linux历史记录简易审计系统

1、有时候我们需要对线上用户的操作进行记录,可以进行追踪,出现问题追究责任,但是linux自带的history并不会实时的记录(仅仅在内存中,当用户正常退出(exit logout )时才会记录到history文件里),并且还有1000行的限制可以删除的&#xff1b; 为了保证让用户的操作进行实时记录&…

chatgpt赋能python:Python入门:如何下载NumPy库

Python入门&#xff1a;如何下载NumPy库 如果你是Python初学者或是有开发经验的工程师&#xff0c;你可能会涉及到使用NumPy库。NumPy是Python中一个非常重要的科学计算库&#xff0c;它提供了高效的多维数组数据结构和数学函数&#xff0c;被许多人用于数据分析、机器学习、科…

Verilog基础:task和function的使用(二)

相关文章 Verilog基础&#xff1a;表达式位宽的确定&#xff08;位宽拓展&#xff09; Verilog基础&#xff1a;表达式符号的确定 Verilog基础&#xff1a;数据类型 Verilog基础&#xff1a;位宽拓展和有符号数运算的联系 Verilog基础&#xff1a;case、casex、ca…

CSS的学习1

使用css的目的是让网页具有美观一致的页面。 语法 CSS规则由两个主要的部分构成&#xff1a;选择器以及一条或多条声明&#xff08;样式&#xff09; 选择器&#xff1a;h3 声明&#xff1a;{color:red;font-size:30px;} 选择器通常是需要改变的HTML元素&#xff1b; 每条…

六一儿童节-王者连连看

庆祝六一 用java实现的连连看练手小游戏 王者连连看 &#x1f923; 游戏介绍&#x1f61c; 核心&#x1f62e;‍&#x1f4a8;总结&#xff1a; &#x1f923; 游戏介绍 王者连连看游戏的目标是通过消除相同的图案来得分并进入更高级别的挑战。   游戏分为四个难度   可以自…

cpp: Visitor Pattern

/*****************************************************************//*** \file Gold.h* \brief 访问者模式 Visitor Pattern C 14 行为模式* 2023年6月8日 涂聚文 Geovin Du Visual Studio 2022 edit.文章来源《C新经典设计模式》 王健伟编著 清华大学出版社* \author…

记录:VS2019+OpenCV4.7.0编译

一、准备 OpenCV官网提供了动态库的安装包&#xff0c;但是只有64位&#xff0c;而且没看到编译参数&#xff0c;如果需要32位或者静态链接还是得自己编译。 CMake&#xff1a;https://cmake.org/download/ Visual Studio&#xff1a;https://visualstudio.microsoft.com/zh…

计算机组成原理——中央处理器

文章目录 **一 CPU的功能和基本结构****1 CPU的功能****2 [基本结构](http://t.csdn.cn/bpCt3)****2.1 运算器****2.2 控制器** **二 指令执行过程****1 指令周期****2 指令周期的数据流****2.1 取指周期****2.2 间址周期****2.3 执行周期****2.4 中断周期** **3 指令的执行方案…

chatgpt赋能python:如何下载Python中的jieba包

如何下载Python中的jieba包 随着Python的普及&#xff0c;很多程序员选择使用Python作为自己的主力编程语言。对于自然语言处理方面的任务&#xff0c;jieba是Python中一个非常优秀的分词工具。那么&#xff0c;如何下载和使用Python中的jieba包呢&#xff1f;本篇文章将为您详…

【MySQL数据库 | 第十四篇】多表查询案例

目录 前言&#xff1a; 引入背景&#xff1a; 练习&#xff1a; 前言&#xff1a; 在第十三篇我们已经详细的介绍了多表查询的类别以及每一个类别的语法&#xff1a;【MySQL数据库 | 第十三篇】多表查询&#xff0c;今天我们将通过案例来巩固我们对多表查询语法的熟悉度。 引入…

JavaSE笔记(二)重制版

面向过程篇 前面我们已经认识了Java语言的相关特性&#xff0c;并且已经成功配置好了开发环境&#xff0c;从这节课开始&#xff0c;我们就可以正式进入到Java语言的学习当中了。Java语言是一门面向对象的语言&#xff0c;但是在面向对象之前&#xff0c;我们还得先学会如何面…

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】

经过以下四个步骤&#xff0c;终于可以开始驱动开发了 01.安装交叉编译环境【附下载地址】 02.IMX6ULL烧写Linux系统 03.设置IMX6ULL开发板与虚拟机在同一网段 04.IMX6ULL开发板与虚拟机互传文件 目录 一、获取内核、编译内核 二、创建vscode工作区&#xff0c;添加内核目录…

《计算机组成原理》期末考试手写笔记——模块五: 并行主存系统(交叉存储器+顺序存储器“带宽”的计算方法)

目录 &#xff08;一&#xff09;知识点总结 &#xff08;二&#xff09;经典考试例题 1.设主存储器容量为256字&#xff0c;字长为32位&#xff0c;模块数m4&#xff0c;分别用顺序方式和交叉方式进行组织。主存储器的存储周期T200ns&#xff0c;数据总线宽度为32位&#x…

EMQ X(2):EMQ X服务端环境搭建与配置

1 安装 EMQ X 目前支持的操作系统: Centos6Centos7OpenSUSE tumbleweedDebian 8Debian 9Debian 10Ubuntu 14.04Ubuntu 16.04Ubuntu 18.04macOS 10.13macOS 10.14macOS 10.15Windows Server 2019 产品部署建议 Linux 服务器&#xff0c;不推荐 Windows 服务器。 安装的方式有…

【Linux】HTTP协议

目录 &#x1f680;前言&#x1f683;HTTP协议 &#x1f684;1、URL网址&#x1f685;2、URL的编码和解码&#x1f687;3、HTTP协议格式&#x1f688;4、HTTP请求&#x1f689;4.1、 HTTP GET和POST方法&#x1f68b;4.2、HTTP状态码&#x1f68a;4.3、HTTP常见Header &#x1…

redis架构设计: redis-server的启动(硬核分析)

怎么在windows上用clion搭建redis的源码阅读环境 请看我的上一篇文章 redis启动之后都干了什么呢? 我们知道&#xff0c;redis的服务端对应的源码位置是server.c main函数是程序启动的入口 &#xff0c;下面我来一行一行的分析server.c的源码 1、定义时间函数变量 struct …