redis rehash

news2025/1/10 16:42:03

在这里插入图片描述

dict结构

dictEntry即键值对,每个桶就是dictEntry连接的链表

typedef struct dictEntry {
  void *key;
  union {
    void *val; // 自定义类型
    uint64_t u64;
    int64_t s64;
    double d;
  } v;
  struct dictEntry *next;
} dictEntry;

数据真正指向的地方

typedef struct dictht {
  dictEntry **table;
  unsigned long size;
  // 计算hash key。sizemask = size-1
  unsigned long sizemask;
  unsigned long used;
} dictht;

dict结构

typedef struct dict {
  dictType *type;
  void *privdata;
  dictht ht[2]; // two hash table for incremental rehashing
  long rehashidx; // rehashing not in progress if rehashidx == -1
  unsigned long iterators; // number of iterators currently running
} dict;

什么时候rehash

扩容条件

扩容发生于打开resizing的情况下超出了使用量/容量的阈值比率(通常是1),或者超出强制扩容比率(5)

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/dict.c#L1030
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    unsigned long idx, table;

    /* Expand the hash table if needed */
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
  
    for (table = 0; table <= 1; table++) {
        // a bunch of operations going on, we are not interested
    }
    return idx;
}

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/dict.c#L54
/* Using dictEnableResize() / dictDisableResize() we make possible to
 * enable/disable resizing of the hash table as needed. This is very important
 * for Redis, as we use copy-on-write and don't want to move too much memory
 * around when there is a child performing saving operations.
 *
 * Note that even when dict_can_resize is set to 0, not all resizes are
 * prevented: a hash table is still allowed to grow if the ratio between
 * the number of elements and the buckets > dict_force_resize_ratio. */
static int dict_can_resize = 1;
static unsigned int dict_force_resize_ratio = 5;

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/dict.c#L987
/* 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 (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio) &&
        dictTypeExpandAllowed(d))
    {
        return dictExpand(d, d->ht[0].used + 1);
    }
    return DICT_OK;
}

有时,如果大量内存被分配超出了可分配内存限制可能会导致redis无法响应,并且可能会驱逐大量的key

// Source: https://github.com/redis/redis/blob/c4b52fc7c9e77fb1f2719d2cb5b9977b90698721/src/server.h#L164
#define HASHTABLE_MAX_LOAD_FACTOR 1.618   /* Maximum hash table load factor. */

// Source: https://github.com/redis/redis/blob/c4b52fc7c9e77fb1f2719d2cb5b9977b90698721/src/dict.c#L977
/* Because we may need to allocate huge memory chunk at once when dict
 * expands, we will check this allocation is allowed or not if the dict
 * type has expandAllowed member function. */
static int dictTypeExpandAllowed(dict *d) {
    if (d->type->expandAllowed == NULL) return 1;
    return d->type->expandAllowed(
                    _dictNextPower(d->ht[0].used + 1) * sizeof(dictEntry*),
                    (double)d->ht[0].used / d->ht[0].size);
}

// Source: https://github.com/redis/redis/blob/c4b52fc7c9e77fb1f2719d2cb5b9977b90698721/src/server.c#L1314
/* Return 1 if currently we allow dict to expand. Dict may allocate huge
 * memory to contain hash buckets when dict expands, that may lead redis
 * rejects user's requests or evicts some keys, we can stop dict to expand
 * provisionally if used memory will be over maxmemory after dict expands,
 * but to guarantee the performance of redis, we still allow dict to expand
 * if dict load factor exceeds HASHTABLE_MAX_LOAD_FACTOR. */
// 若使用比率小于等于HASHTABLE_MAX_LOAD_FACTOR,就回检查释放超出可分配最大内存
int dictExpandAllowed(size_t moreMem, double usedRatio) {
    if (usedRatio <= HASHTABLE_MAX_LOAD_FACTOR) {
        return !overMaxmemoryAfterAlloc(moreMem);
    } else {
        return 1;
    }
}

缩容条件

若hash table大于DICT_HT_INITIAL_SIZE = 4并且负载因子小于0.1,那么就会进行缩容

/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_SIZE     4

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/server.h#L162
/* Hash table parameters */
#define HASHTABLE_MIN_FILL        10      /* Minimal hash table fill 10% */

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/dict.h#L147
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/server.c#L1498
int htNeedsResize(dict *dict) {
    long long size, used;

    size = dictSlots(dict);
    used = dictSize(dict);
    return (size > DICT_HT_INITIAL_SIZE &&
            (used*100/size < HASHTABLE_MIN_FILL));
}

// Source: https://github.com/redis/redis/blob/c4b52fc7c9e77fb1f2719d2cb5b9977b90698721/src/dict.c#L133
/* Resize the table to the minimal size that contains all the elements,
 * but with the invariant of a USED/BUCKETS ratio near to <= 1 */
int dictResize(dict *d)
{
    unsigned long minimal;

    if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
    minimal = d->ht[0].used;
    if (minimal < DICT_HT_INITIAL_SIZE)
        minimal = DICT_HT_INITIAL_SIZE;
    return dictExpand(d, minimal);
}

“虚假”的rehash

在满足rehash条件之后会进行rehash,但这个rehash并不是真正的搬迁过程

/* Expand or create the hash table,
 * when malloc_failed is non-NULL, it'll avoid panic if malloc fails (in which case it'll be set to 1).
 * Returns DICT_OK if expand was performed, and DICT_ERR if skipped. */
int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
{
    if (malloc_failed) *malloc_failed = 0;

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
  	// 若使用元素数量大于容量,或者已经rehash,报错
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; /* the new hash table */
  	// 按照原容量两倍扩展新容量
    unsigned long realsize = _dictNextPower(size);

    /* Rehashing to the same table size is not useful. */
  	// rehash的容量等于原容量是没有意义的,直接返回
    if (realsize == d->ht[0].size) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
  	// 初始化新hash table
    n.size = realsize;
    n.sizemask = realsize-1;
    if (malloc_failed) {
        n.table = ztrycalloc(realsize*sizeof(dictEntry*));
        *malloc_failed = n.table == NULL;
        if (*malloc_failed)
            return DICT_ERR;
    } else
        n.table = zcalloc(realsize*sizeof(dictEntry*));
		
  	// 初始化新hash table使用量为0
    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
  	// 若dict本来是空的,那么直接将初始化的新hash table赋予给dict
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
  	// 赋予新hash table给dict的第二个hash table(也就是rehash hash table),并标记开始rehash
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

/* return DICT_ERR if expand was not performed */
int dictExpand(dict *d, unsigned long size) {
    return _dictExpand(d, size, NULL);
}

真正rehash过程

在每次对dict进行添加、删除、查找或者更新操作时会执行真正的rehash

/* Performs N steps of incremental rehashing. Returns 1 if there are still
 * keys to move from the old to the new hash table, otherwise 0 is returned.
 *
 * Note that a rehashing step consists in moving a bucket (that may have more
 * than one key as we use chaining) from the old to the new hash table, however
 * since part of the hash table may be composed of empty spaces, it is not
 * guaranteed that this function will rehash even a single bucket, since it
 * will visit at max N*10 empty buckets in total, otherwise the amount of
 * work it does would be unbound and the function may block for a long time. */
// n代表至多有n个桶被rehash
int dictRehash(dict *d, int n) {
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
  	// 若未处于rehash过程中,返回
    if (!dictIsRehashing(d)) return 0;

  	// 遍历dict直到使用量为0或者达到本次最大遍历桶数量
    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        // 跳过空桶,若超过可允许访问的空桶数量也返回
        while(d->ht[0].table[d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
      	// de指向当前rehash节点
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        while(de) {
            uint64_t h;

          	// 记录当前桶节点下一个
            nextde = de->next;
            /* Get the index in the new hash table */
          	// 获取搬迁新桶索引
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
            // 当前桶节点指向新桶头节点
            de->next = d->ht[1].table[h];
          	// 搬迁当前桶节点到新桶
            d->ht[1].table[h] = de;
          	// 原桶使用量-1,新桶+1
            d->ht[0].used--;
            d->ht[1].used++;
          	// 更新遍历的桶节点
            de = nextde;
        }
      	//清空当前搬迁桶,并更新搬迁索引
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    /* Check if we already rehashed the whole table... */
  	// 若全部搬迁完毕,则清空原hash table,并将新hash table置为当前table,清空搬迁状态
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1];
        _dictReset(&d->ht[1]);
        d->rehashidx = -1;
        return 0;
    }

    /* More to rehash... */
    return 1;
}

此外redis在空闲的时候也会充分利用起来进行rehash

/* Our hash table implementation performs rehashing incrementally while
 * we write/read from the hash table. Still if the server is idle, the hash
 * table will use two tables for a long time. So we try to use 1 millisecond
 * of CPU time at every call of this function to perform some rehashing.
 *
 * The function returns 1 if some rehashing was performed, otherwise 0
 * is returned. */
// 当其他内存繁重的进程(比如将当前数据库状态保存到磁盘)无法运行时,所有 redis 数据库的后台 cron 作业将一个接一个地调用 IncrementallyRehash () ,并且在配置中打开 activerhash 标志。
int incrementallyRehash(int dbid) {
    /* Keys dictionary */
    if (dictIsRehashing(server.db[dbid].dict)) {
        dictRehashMilliseconds(server.db[dbid].dict,1);
        return 1; /* already used our millisecond for this loop... */
    }
    /* Expires */
    if (dictIsRehashing(server.db[dbid].expires)) {
        dictRehashMilliseconds(server.db[dbid].expires,1);
        return 1; /* already used our millisecond for this loop... */
    }
    return 0;
}

// Source: https://github.com/redis/redis/blob/b8c67ce41b51247c02a639929e4fab20189afa1e/src/dict.c#L263 
/* Rehash in ms+"delta" milliseconds. The value of "delta" is larger 
 * than 0, and is smaller than 1 in most cases. The exact upper bound 
 * depends on the running time of dictRehash(d,100).*/
int dictRehashMilliseconds(dict *d, int ms) {
    if (d->iterators > 0) return 0;

    long long start = timeInMilliseconds();
    int rehashes = 0;

  // 在ms时间内运行rehash
    while(dictRehash(d,100)) {
        rehashes += 100;
        if (timeInMilliseconds()-start > ms) break;
    }
    return rehashes;
}

总结

redis rehash利用了和golang类似的方式,将真正的rehash操作放置到了dict增删改除中,有效地分摊了rehash性能消耗。

不同之处在于

  • 更进一步作为一个数据库应用产品利用了闲暇时间去进行rehash。

  • 为有效利用宝贵内存空间,添加了缩容,还添加可以在较高的使用率下的内存回收

  • 扩容前预估是否超出内存限制

  • 出于对响应时间的敏感性,redis还设置了最大空桶访问次数

  • redis没有考虑到桶overflow太多的情况

Ref

  1. https://codeburst.io/a-closer-look-at-redis-dictionary-implementation-internals-3fd815aae535

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

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

相关文章

京东网站登录二维码显示不出来

环境&#xff1a; 360急速浏览器 Win10专业版 问题描述&#xff1a; 京东网站登录二维码显示不出来 解决方案&#xff1a; 1.打开安全卫士 2.功能大全找到断网急救箱 3.全面诊断一下有问题修复一下&#xff0c;重启浏览器解决

数字迷宫:探秘统计位数为偶数的奇妙世界

本篇博客会讲解力扣“1295. 统计位数为偶数的数字”的解题思路&#xff0c;这是题目链接。 统计位数是偶数的数据个数&#xff0c;关键在于如何统计一个整数的位数。方法是&#xff1a;对于一个整数n&#xff0c;每次/10&#xff0c;都会缩小一位&#xff0c;所以一直进行/10操…

【爬虫】AOI

目前几个大厂&#xff0c;高德百度腾讯&#xff0c;都支持POI爬取&#xff0c;而AOI是需要自己找接口的。 换言之&#xff0c;爬虫需谨慎 1 百度AOI 参考链接是&#xff1a; 这两个链接是选定范围爬取范围内选定类别的AOI 黑科技 | 百度地图抓取地块功能&#xff08;上&#x…

DeepSpeed-Chat 打造类ChatGPT全流程 笔记二之监督指令微调

文章目录 系列文章0x0. 前言0x1. &#x1f415; Supervised finetuning (SFT) 教程翻译&#x1f3c3; 如何训练模型&#x1f3c3; 如何对SFT checkpoint进行评测?&#x1f481; 模型和数据☀️来自OPT-1.3B及其SFT变体&#xff08;使用不同微调数据&#xff09;的提示示例☀️…

关于layui实现按钮点击添加行的功能

关于layui实现按钮点击添加行的功能 实现效果 代码实现 <!DOCTYPE html> <html lang"zh" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><title>Title</title><link rel"styl…

帅气的头像-InsCode Stable Diffusion 美图活动一期

1.运行地址 Stable Diffusion 模型在线使用地址&#xff1a; https://inscode.csdn.net/inscode/Stable-Diffusion 界面截图&#xff1a; 2.模型版本及相关配置 模型&#xff1a;chilloutmix-Ni.safetensor [7234b76e42] 采样迭代步数&#xff08;steps&#xff09;: 30 采样…

QtDesigner的使用

QtDesigner的使用 1、快速入门2、布局管理 1、快速入门 主窗口 菜单栏、工具栏、状态栏 快捷预览方式&#xff0c;工具箱 对象查看器 属性编辑器 英文名作用objectName控件对象名称geometry相对坐标系sizePolicy控件大小策略minnimumSize最小宽度、高度maximumSize最大宽度…

基于jsp+Servlet+mysql学生信息管理系统V2.0

基于jspServletmysql学生信息管理系统V2.0 一、系统介绍二、功能展示1.项目骨架2.数据库表3.项目内容4.登陆界面5.学生-学生信息6、学生-修改密码7、管理员-学生管理8、管理员-添加学生9.管理员-修改学生信息10.管理员-班级信息11.管理员-教师信息 四、其它1.其他系统实现五.获…

旅游卡系统旅行社小程序APP

旅游业的不断发展&#xff0c;旅游卡系统、旅行社小程序APP等数字化工具已经成为了旅行社提升业务效率、提高客户体验的重要手段。下面&#xff0c;我们将为您介绍旅游卡系统旅行社小程序APP的相关内容。 一、旅游卡系统 旅游卡系统是一种将旅游门票、优惠券等资源整合…

实时包裹信息同步:WebSocket 在 Mendix 中的应用

场景介绍 在现代物流中&#xff0c;能够实时跟踪包裹信息&#xff0c;尤其是包裹重量&#xff0c;是非常重要的。在这种场景中&#xff0c;我们可以使用称重设备获取包裹的信息&#xff0c;然后实时将这些信息同步给 Mendix 开发的 App&#xff0c;并在 App 的页面上实时显示包…

获取java对象被更新的属性和值

业务场景 更新User信息后&#xff0c;需要收集哪些字段的哪些值被更新了。 思路 比较originUser和newUser&#xff0c;收集值不同的属性以及变化前后的值。 代码实现 public static Map<String, Map<String, Object>> getChange(Object originObject, Object ne…

Java 9 - 模块化系统

定义要导出的模块 package com.lfsun.java9study.mypackage;public class MyClass {public static void sayHello() {System.out.println("Hello from com.lfsun.java9study.mypackage!");}public static void main(String[] args) {System.out.println("test&…

【Linux】LVS负载均衡群集 DR模式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 LVS负载均衡群集 DR模式 数据包流向分析DR 模式的特点LVS-DR中的ARP问题VIP地址相同导致响应冲突返回报文时源地址使用VIP&#xff0c;导致网关设备的ARP缓存表紊乱 DR模式 L…

Pycharm配置解释器(interpreter)

关于pycharm编译器的解释器&#xff0c;网友朋友的分享 Pycharm配置&#xff08;1&#xff09;——解释器&#xff08;interpreter&#xff09; 详细了解PyCharm支持的4种Python Interpreter和配置方法 对大多数人而言就只需要 分清虚拟解释器和系统解释器&#xff0c;使用虚拟…

关闭Win10的预览窗口

关闭Win10的预览窗口 每次拖拽文件都显示那个黑边的模型一样的东西&#xff0c;通过上面可以关闭该功能。

MBD开发 STM32 外部仿真

要接一个串口&#xff0c;用来设备和simulink的通信 烧录即可 烧录时选择串口

.360勒索病毒的加密数据怎么处理?|.360勒索病毒数据解密恢复

.360勒索病毒是一种恶意软件&#xff0c;它对用户的计算机文件进行加密&#xff0c;并要求支付赎金才能解密恢复数据。这种勒索病毒以其广泛传播和严重破坏性而闻名&#xff0c;给个人用户和企业带来了巨大的困扰和损失。 一家中型制造企业&#xff0c;名为XYZ制造有限公司&…

用科技智造新未来!在线开发平台强力助推数字化发展

在科技智造新时代&#xff0c;科技的力量是无处不见的。运用科技可以创造美好的生活&#xff0c;可以实现数字化发展&#xff0c;帮助企业实现流程化管理。在线开发平台将科技元素注入到平台中&#xff0c;将科技与办公需求相连接&#xff0c;创造高效率办公及流程化发展。 1…

# hub.docker.com镜像加速站点仓库的搭建与使用

hub.docker.com镜像加速站点仓库的搭建与使用 文章目录 hub.docker.com镜像加速站点仓库的搭建与使用1 镜像仓库搭建1.1 创建目录及docker-compose.yaml1.2 如果要开启密码验证&#xff0c;需要先生成密码1.3 启动服务 2 用户端使用3 最后 前段时间由于拉取docker镜像&#xff…

初出茅庐的小李博客之链表知识2

链表知识点2 通过malloc函数动态创建节点&#xff1a; #include <stdio.h> #include <stdlib.h>typedef struct List Node_t;struct List {/*数据区域有两个变量*/int a;float b;/*地址区域有一个指针*/Node_t* pNext; };Node_t Head_Node; //表头变量 Node_t Bo…