redis之hash源码分析

Redis的Hash冲突以及渐进式ReHash源码分析 Redis数据DB       Redis是一个一个的DB,那么这个DB到底是一个什么样结构的数据呢? ​      如下是Redis官方的源码(5.0) /* Redis数据库表示。有多个数据库标识从0(默认数据库)到配置的最大值的整数数据库。数据库号是结构中的“id”字段*/ typedef struct redisDb { dict *dict; 阅读详情

hash的对象编码

hash数据类型的对象编码有两种,分别是OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_HT,即一种是以压缩数组;一种是哈希字典。两者的数据格式见下图:
在这里插入图片描述
图2:
在这里插入图片描述

编码转换

redis的hash数据之所以使用OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_HT两种编码格式,是为了当一个hash对象的键值对数据量比较小时,使用紧凑的数组格式来节省内存空间。

因此,当一个hash对象的键值对数据量增加到临界值时,就会触发编码转换,将该hash对象的键值对从OBJ_ENCODING_ZIPLIST编码转换为OBJ_ENCODING_HT编码。其中的临界值的关键参数为hash-max-ziplist-entries和hash-max-ziplist-value,可在配置文件中修改该两个参数的默认值。

# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

hash-max-ziplist-entries

hash-max-ziplist-entries的默认值为512,表示当hash对象的键值对数量大于该值时使用OBJ_ENCODING_HT编码,否则使用OBJ_ENCODING_ZIPLIST编码。

如下测试,向hash对象中添加512个键值对,通过debug命令输出该hash对象的编码格式为encoding:ziplist,再添加一个键值对导致hash对象中的键值对数量大于hash-max-ziplist-entries时,该hash对象的编码格式转换为了encoding:hashtable。

127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i) end" 1 "members"
(nil)

127.0.0.1:6379> hlen members
(integer) 512

127.0.0.1:6379> debug object members
Value at:0x7fe27c2b34a0 refcount:1 encoding:ziplist serializedlength:2838 lru:2137578 lru_seconds_idle:7

127.0.0.1:6379> hset members 513 513
(integer) 1

127.0.0.1:6379> debug object members
Value at:0x7fe27c2b34a0 refcount:1 encoding:hashtable serializedlength:2826 lru:2137611 lru_seconds_idle:2

关键源码如下:

void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;

    if (o->encoding != OBJ_ENCODING_ZIPLIST) return;

    for (i = start; i <= end; i++) {
        if (sdsEncodedObject(argv[i]) &&
        // 当key或者value的长度大于hash-max-ziplist-value时转换编码
            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) 
        {
            hashTypeConvert(o, OBJ_ENCODING_HT);
            break;
        }
    }
}

hash-max-ziplist-value

hash-max-ziplist-value的默认值为64,表示hash对象中的键值对存在键的长度或值的长度大于该值时使用OBJ_ENCODING_HT编码,否则使用OBJ_ENCODING_ZIPLIST编码。

如下测试,向hash对象中添加值的长度为64的键值对时,该hash对象的编码格式为encoding:ziplist。再重新添加一个值的长度为65的键值对时,hash对象的编码格式变成了encoding:hashtable。

127.0.0.1:6379> hset members f1 1234567890123456789012345678901234567890123456789012345678901234
(integer) 1
127.0.0.1:6379> debug object members
Value at:0x7fe27c2b3490 refcount:1 encoding:ziplist serializedlength:38 lru:2137862 lru_seconds_idle:3

127.0.0.1:6379> hset members f1 12345678901234567890123456789012345678901234567890123456789012345
(integer) 0
127.0.0.1:6379> debug object members
Value at:0x7fe27c2b3490 refcount:1 encoding:hashtable serializedlength:26 lru:2137871 lru_seconds_idle:6

关键源码如下:

int hashTypeSet(robj *o, sds field, sds value, int flags) {
    int update = 0;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        ...
        
        // 当存放的键值对数量大于hash-max-ziplist-entries时转换编码
        /* Check if the ziplist needs to be converted to a hash table */
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    }
    ...
}

扩容

当键值对数量超过hash槽长度的5倍时hash表扩容,扩容后为hash槽长度为大于键值对数量2倍大小的最小2的指数,即4、8、16、32、64…

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

/* 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; // 哈希表table的大小
    unsigned long sizemask; // 用于位运算计算key对应的table位置,sizemask=size-1
    unsigned long used; // 哈希表中键值对的数量
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; // 表示rehash时hash槽下次迁移数据的槽的位置,-1表示非rehash状态
    unsigned long iterators; /* number of iterators currently running */
} dict;
#define DICT_HT_INITIAL_SIZE     4
static unsigned int dict_force_resize_ratio = 5;

static int _dictExpandIfNeeded(dict *d)
{
    // 正在rehash,直接返回
    if (dictIsRehashing(d)) return DICT_OK;

    // 刚初始化的hash表,hash槽长度为0,初始化为槽长度为4的hash表
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    // 当键值对数量超过hash槽长度的5倍时,扩容
    // 扩容后为hash槽长度为键值对数量2倍大小的hash表
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}

int dictExpand(dict *d, unsigned long size)
{
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; 
    // 真正扩容大小永远是2的指数且不大于LONG_MAX
    unsigned long realsize = _dictNextPower(size);

    if (realsize == d->ht[0].size) return DICT_ERR;

    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;

    // 未初始化的hash表直接创建
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    // 已经初始化的hash表,通过rehash实现扩容
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

rehash

redis的rehash最大的特点是该操作是渐进式的。redis是个单线程模型,对于数据量大的hash数据做耗时的rehash操作时会导致redis的长时间阻塞。因此redis对hash数据的rehash操作设计成了渐进式,即一个hash对象有两个hash表,当需要rehash操作时一点一点将旧hash表中的数据迁移到新hash表中。虽然慢了一点,但是不会造成长时间的阻塞。

当一个rehash状态的hash数据收到一个增删改查请求时,都会先进行一次渐进式rehash,再处理该增删改查操作。每次渐进式rehash会迁移10个hash槽中的数据。

当hash表处于扩容状态时,每次查询都会先读取旧hash表,旧hash表查询不到时再读取新hash表,即新旧hash表遍历查询。每次增删改则尝试操作旧hash表,旧hash表查询不到该数据时时则再操作新hash表。

static void _dictRehashStep(dict *d) {
    // 渐进式rehash每次迁移10个hash槽的数据
    if (d->iterators == 0) dictRehash(d,1);
}

int dictRehash(dict *d, int n) {
    int empty_visits = n*10; 
    if (!dictIsRehashing(d)) return 0;

    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;

        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 = d->ht[0].table[d->rehashidx];
        while(de) {
            uint64_t h;

            nextde = de->next;
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    // rehash完成,ht[1]指向ht[0],ht[1]初始化为空hash表
    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;
    }

    return 1;
}
dictEntry *dictFind(dict *d, const void *key)
{
    dictEntry *he;
    uint64_t h, idx, table;

    // 如果新旧hash表都为空,直接返回null
    if (d->ht[0].used + d->ht[1].used == 0) return NULL; 
    // 如果正在rehash,做一次渐进式rehash迁移10个hash槽的数据
    if (dictIsRehashing(d)) _dictRehashStep(d);
    h = dictHashKey(d, key);
    // 如果正在rehash,则尝试从两个hash表获取数据
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) return NULL;
    }
    return NULL;
}

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;
    
    // 如果正在rehash,做一次渐进式rehash迁移10个hash槽的数据
    if (dictIsRehashing(d)) _dictRehashStep(d);

    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    // 如果正在rehash,则把新数据添加到新hash表
    // 否则添加到ht[0]
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    dictSetKey(d, entry, key);
    return entry;
}
基于光电传感的非接触式酒精浓度检测系统设计与实现 本文详细介绍了基于光电传感技术的非接触式酒精浓度检测系统设计与实现。系统利用940nm红外LED和OPT101光电传感器,结合朗伯-比尔定律,通过STM32微控制器进行信号采集与处理,实现了快速、准确的酒精浓度测量,测量精度达±1%,适用于工业检测和便携式应用场景。 阅读详情

相关推荐

Obsidian + AI 自动知识管理:手把手教程

装完后,在命令面板(Ctrl+P)搜 "Generate Summary",AI 会在当前笔记末尾追加一段摘要。你一个月前写的、现在想快速回顾的笔记长篇笔记(三千字以上)开会记录这类信息密集的内容过度自动化。有人装上插件后,所有笔记都让 AI 打标签、写摘要、找关联。最后笔记库看起来整整齐齐,但全都是 AI 的链接,你自己完全没参与。这样做的后果是什么?你写过的笔记跟你没有"真实连接"。你还是记不住。你还是不知道自己的知识库里有什么。AI 推荐,你决策。

weixin_43571227的博客 747

Instant Neural Graphics Primitives with a Multiresolution HashEncoding_具有多分辨率的哈希编码

痛点:完全连接的神经网络的参数训练和评估的成本都很高。 目的:降低成本——在不牺牲质量的情况下,减少浮点和内存访问操作的数量,搭建小型神经网络。通过使用完全融合的CUDA内核来实现整个系统,并专注于最小化浪费的带宽和计算操作。 过程:利用可训练特征向量的多分辨的哈希表,其值通过随机梯度下降进行优化。 要素:多分辨率结构允许网络消除哈希冲突的歧义。

MengYa_Dream的博客 5525

基于高精度三维机器视觉的新能源汽车锂电池表面缺陷检测

在机器视觉行业迅速发展的背景下,市场上提供了功能强大且种类齐全的3D相机系列,可以满足锂电池从电芯到模组各个工艺和工位的3D检测需求。

Hinyeung_Limited的博客 1131

Redis——Cluster数据分布算法&哈希槽

实现redis集群模式下,在存在多个master节点的时候,数据如何分布在这些节点上去;为了解决这个问题,就引入了redis数据分布的算法;数据分布的算法有一个演变过程,即hash算法、一致性hash算法、redis cluster哈希槽算法;本篇介绍这几种Redis集群的数据分片算法;...............

娜娜米的博客 3504

Win10/Win11系统下,用VSCode编译Betaflight固件最全避坑指南(从GCC安装到HEX生成)

本文详细介绍了在Win10/Win11系统下使用VSCode编译Betaflight固件的完整流程,包括GCC安装、环境变量配置、终端选择、Make工具安装等关键步骤,帮助开发者避开常见陷阱,高效生成HEX文件。特别针对VSCode环境优化和编译错误排查提供了实用解决方案。

weixin_42557803的博客 415

产品经理必备知识——API接口

在古代,我们的传输信息的方式有很多,比如写信、飞鸽传书,以及在战争中使用的烽烟,才有了著名的烽火戏诸侯,但这些方式传输信息的效率终究还是无法满足高速发展的社会需要。如果你选择允许,app就会根据你的当前位置为你提供更加个性化的一些服务,当然也有一些app拿到你的位置信息做出损害用户利益的行为,所以还是需要谨慎授权。首先,看接口文档的场景一般是自身业务模块需要获取外部的数据,但又不想自己重新开发,寻找外部已经实现的有开放能力的供应商(包含企业内部的)。通过发送请求和响应消息的方式实现程序间的通信。

WBKJ_Noah_的博客 3454

HashEncode哈希加密

HashEncode哈希加密帮助类库源码下载: [C#] 纯文本查看 复制代码 ? 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

xiaobaibai1216的专栏 911

PyTorch GPU版本安装避坑指南:解决RuntimeError: No CUDA GPUs are available报错

本文详细解析了PyTorch GPU版本安装过程中常见的`RuntimeError: No CUDA GPUs are available`报错问题,从硬件驱动检查、CUDA Toolkit与cuDNN安装到PyTorch GPU版本的正确配置,提供了全面的避坑指南和实战验证方法,帮助开发者高效搭建深度学习开发环境。

r2s3t4的博客 295

instant npg

pytorch_fwd首先获取到每个resolution下的坐标对应的grid的八个顶点坐标经过hash function的array entry的index,即hashed_0-hashed_7,形状[…hash函数首先进行了一个乘法操作,然后进行xyz的异或,再通过求模保证其index范围再hash_table_size内,再通过offset将不同尺度下的index平移到各自所在的hash_table_size大小的数组连续块中。接着访问八个顶点的尺度特征,f_0-f_7,形状[…

atomexplore的博客 309

宝塔部署若依前端出现502解决方法

若依系统是一个基于Java语言的开源项目,旨在帮助开发者减少开发时间,特别适用于需要快速开发出一套具有用户管理、菜单管理、权限管理、定时任务、日志管理等功能的简单系统。系统分为前后端分离、分布式等架构前后端分离部署教程然而通过宝塔来部署若依系统会遇见一些问题,特此在这里记录一下。

m0_74823683的博客 776

第二十届全国大学生智能汽车竞赛极速光电组:MicroPython与恩智浦RT1021的硬件编程实战指南

本文为参加第二十届全国大学生智能汽车竞赛极速光电组的同学提供了一份详尽的MicroPython与恩智浦RT1021硬件编程实战指南。文章深入剖析了选择MicroPython与RT1021这对组合的优势,详细讲解了从开发环境搭建、基础硬件控制(如GPIO、PWM、编码器、CCD)到系统集成(平衡控制、循迹算法)与多任务调试的全过程,旨在帮助参赛者降低嵌入式开发门槛,高效备赛。

wdx0123456的博客 842

redis t_hash哈希对象的源码分析

哈希对象的编码可以是ziplist或者hashtable。

wade1991的专栏 1155

rk3588平台USB 3.0 -OAK深度相机适配方法

摘要:本文记录了OAK摄像头设备的配置与测试流程。主要内容包括:1)添加USB设备udev规则;2)拉取相关依赖库;3)安装Python3和pip;4)安装项目依赖;5)安装FFmpeg工具;6)测试摄像头功能并转换视频格式;7)将测试视频拷贝到U盘查看。操作步骤涵盖了从设备驱动配置到功能测试的完整流程,提供了详细的命令行操作指引。通过该文档可快速完成OAK摄像头的环境搭建和基础功能验证。

yy_hear的博客 324

Redis的Hash实现

一 、Hash 1.1 介绍 Redis中的字典采用哈希表作为底层实现,一个哈希表有多个节点,每个节点保存一个键值对。 在Redis源码文件中,字典的实现代码在dict.c和dict.h文件中。 Redis的数据库就是使用字典作为底层实现的,通过key和value的键值对形式,代表了数据库中全部数据。而且,所有对数据库的增、删、查、改的命令,都是建立在对字典的操作上。 同时,字典还是Redi...

jayzym的博客 1615

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

redis源码分析(五)、redis命令学习总结—哈希Hash

一、介绍hasp: 大多数编程语言中的map数据结构一样,Redis是键值对的集合,也就是说它存放的是字符串和字符串之间的映射。由于这个特性,hash特别适用于存储一个对象。将一个对象存储在hash中会占用更少的内存,并且可以方便地存取整个对象。 二、命令学习 1、Hset Redis Hset 命令用于为哈希表中的字段赋值 。 如果哈希表不存在,一个新的哈希表被创建

chen 606

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Redis 源码分析哈希对象(z_hash)

数据结构 Redis 的 hash 对象采用了两种方式来实现,前面分析过连续内存和非连续内存的优缺点,这里 hash 列表也折中了两种情况。 两种存储结构(代码位置 src/t_hash.c): ziplist 编码: ziplist 编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入压缩列表表尾,然后在将保存了值的压缩列表表尾,因此: 1、保存了同一个键值对的两个节点总是紧挨在一起,保存键的节点在前面,保存值的节点在后面。 2、先添加到哈希对

CRMEB小程序商城的博客 1176

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Redis源码分析(八)--- t_hash哈希转换

在上次的zipmap分析完之后,其实关于redis源代码结构体部分的内容其实已经全部结束了,因为下面还有几个和结构体相关的操作类,就页把他们归并到struct包下了。这类的文件有:t_hash.c,z_list,z_set.c,t_string.c,t_zset.c,这些文件的功能其实都差不多,就是用来实现Client和Server之间的命令处理的操作类,通过robj的形式,把dict,zipli

走在前往架构师的路上 4661

redis源码分析之对象系统源码分析-string, list链表,hash哈希,set集合,zset有序集合

前言 在redis中使用五种数据结构都封装成对象系统 分析流程 redis对象数据结构介绍和对应数据使用编码格式 string 介绍 list链表底层实现原理 hash哈希底层实现原理 set集合底层实现原理 zset有序集合底层实现原理 正文 一, redis对象数据结构介绍和对应数据使用编码格式 redis中对象的数据结构 typedef struct redisObject { ...

永远积极向上、永远热泪盈眶、永远豪情满怀、永远坦坦荡荡 448

Redis源码分析(十)——哈希表类型t_hash

哈希表是HSET、HLEN等命令的操作对象,他使用REDIS_ENCODING_ZIPLIST(压缩列表)和REDIS_ENCODING_HT(字典)两种编码方式。 当哈希表使用字典编码时,程序将哈希表的键保存为字典的键,将哈希表的值保存为字典的值。哈希表使用的字典的键和值都是字符串对象。 当哈希表使用压缩列表编码时,程序通过将键和值交叉存入压缩列表中,从而形成保存哈希表需所需的键——值对结构

艰难困苦如同欢乐,终将成为人生最后的财富 947

五:redis的hash数据类型常用命令、场景、源码分析

五:redis的hash数据类型常用命令、场景、源码分析 哈希数据类型存储key在field和value中,如果存储一个用户的信息key是user:id,field为nickname,age,sex,value存储对应的值,可以快速查找用户信息的某个属性。 命令 hset 解释:设置/更新hash类型的键值对 用法: hset key field value 本文分析的源码是基于redis...

勿在浮沙筑高台 335

redis源码分析 - 字典哈希

redis源码分析,字典的设计与实现

猫步旅人- 程序员的旅途 981

Redis源码分析-基础功能(hash)

Redis(hash)基础结构typedef struct dict {    dictType *type;    void *privdata;    dictht ht[2];    long rehashidx; /* rehashing not in progress if rehashidx == -1 */    int iterators; /* number of iterato...

karen_zy的博客 487

redis源码分析与思考(十五)——哈希类型的命令实现(t_hash.c)

哈希类型又叫做字典,在redis中,哈希类型本身是一个键值对,而哈希类型里面也存贮着键值对,其对应关系是,每个哈希类型的值对应着一个键值对或多对键值对,如图所示: ...

溯夜,逆流而上 458

Redis源码分析(三)---dict哈希结构

昨天分析完adlist的Redis代码,今天马上马不停蹄的继续学习Redis代码中的哈希部分的结构学习,不过在这里他不叫什么hashMap,而是叫dict,而且是一种全新设计的一种哈希结构,他只是通过几个简单的结构体,再搭配上一些比较常见的哈希算法,就实现了类似高级语言中HashMap的作用了。也让我见识了一些哈希算法的实现,比如dbj hash的算法实现,俗称times33,算法,就是不停的*3

走在前往架构师的路上 1万+

Redis系列-哈希表

本文将对redis哈希表进行介绍,主要侧重从源代码的角度展开,所使用的源码版本为6.0.19

zhuohuashiyi 1006
上一篇: 如何在 .NetCore 中使用 AutoMapper 高级功能
下一篇: redis之key过期源码分析
.NET跨平台
博客等级 码龄15年 6066粉丝 738原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值