Redis数据类型 — Hash

news2024/11/17 2:52:44

目录

Hash内部实现

源码片段

Hset执行函数

创建hash对象

尝试转换存储空间的编码

遍历把键值对根据存储空间格式进行存储起来


Hash 是一个键值对(key - value)集合,Hash 特别适合用于存储对象。

Hash内部实现

Hash 类型的底层数据结构是由压缩列表或哈希表实现的,Hash结构默认采用ZipList编码,用以节省内存。 ZipList中相邻的两个entry 分别保存field和value

  • 如果哈希类型元素个数小于 512 个(默认值,可由 hash-max-ziplist-entries 配置),所有值小于 64 字节(默认值,可由 hash-max-ziplist-value 配置)的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构;
  • 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的 底层数据结构。

在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由listpack数据结构来实现了

从底层结构来看,只要Zset把SkipList去掉,Hash底层采用的编码与Zset基本一致

Hash结构与Redis中的Zset非常类似:

  • 都是键值存储

  • 都需求根据键获取值

  • 键必须唯一

区别如下:

  • zset的键是member,值是score;hash的键和值都是任意值

  • zset要根据score排序;hash则无需排序

源码片段

Hset执行函数

void hsetCommand(client *c) {
    int i, created = 0;
    robj *o;
    
    if ((c->argc % 2) == 1) {
        addReplyErrorFormat(c,"wrong number of arguments for '%s' command",c->cmd->name);
        return;
    }
    //判断hash的key是否存在,不存在则创建一个新的,默认是ZipList编码
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
    //判断是否需要把ZipList转为Dict
    hashTypeTryConversion(o,c->argv,2,c->argc-1);
    //依次写入field-value对,并执行hset命令
    for (i = 2; i < c->argc; i += 2)
        created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);

    /* HMSET (deprecated) and HSET return value is different. 
     HMSET(已弃用)和 HSET 返回值不同*/
    char *cmdname = c->argv[0]->ptr;
     //找命令是hset还是hmset,
    if (cmdname[1] == 's' || cmdname[1] == 'S') {
        /* HSET */
        addReplyLongLong(c, created);
    } else {
        /* HMSET */
        addReply(c, shared.ok);
    }
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
    server.dirty += (c->argc - 2)/2;
}

创建hash对象

//取出或新创建哈希对象,如果是新建相当于创建了一个Dict
robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
	//首先根据key去取
    robj *o = lookupKeyWrite(c->db,key);
    if (checkType(c,o,OBJ_HASH)) return NULL;
	//如果不存在则会新创建一个hash对象
    if (o == NULL) {
        o = createHashObject();
        dbAdd(c->db,key,o);
    }
    return o;
}

robj *createHashObject(void){
    //默认采用ZipList编码,申请ZipList内存空间
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_HASH, zl);

    //设置编码
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

尝试转换存储空间的编码

当数据量较大时,Hash结构会转为HT编码,也就是Dict,触发条件有两个:

  • ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)

  • ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节)

当满足上面两个条件其中之⼀的时候,Redis就使⽤dict字典来实现hash。 Redis的hash之所以这样设计,是因为当ziplist变得很⼤的时候,它有如下几个缺点:

  • 每次插⼊或修改引发的realloc操作会有更⼤的概率造成内存拷贝,从而降低性能。

  • ⼀旦发生内存拷贝,内存拷贝的成本也相应增加,因为要拷贝更⼤的⼀块数据。

  • 当ziplist数据项过多的时候,在它上⾯查找指定的数据项就会性能变得很低,因为ziplist上的查找需要进行遍历。

总之,ziplist本来就设计为连续的内存空间,这种结构并不擅长做修改操作。⼀旦数据发⽣改动,就会引发内存realloc,可能导致内存拷贝。

void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;
    size_t sum = 0;

    //如果对象不是 ziplist 编码,那么直接返回
    if (o->encoding != OBJ_ENCODING_ZIPLIST) return;

    /*遍历所有对象,一个一个比较长度,字符串值是否超过指定长度,记住这里是比较链表
      中每一个值的长度,如果有一个大于server.hash_max_ziplist_value,则把ziplist
      转换成hashtable*/
    for (i = start; i <= end; i++) {
        if (sdsEncodedObject(argv[i]) &&
            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
        {
            hashTypeConvert(o, OBJ_ENCODING_HT);
            break;
        }
        sum += len;        
    }
    //Ziplist大小超过1G,也转为哈希表
    if(!ziplistSafeToAdd(o->ptr, sum))
        hashTypeConvert(o, OBJ_ENCODING_HT);
}

遍历把键值对根据存储空间格式进行存储起来

int hashTypeSet(robj *o, sds field, sds value, int flags){
    int update = 0;
    //判断是否为ZipList编码
    if(o->encoding == OBJ_ENCODING_ZIPLIST){
        unsigned char *zl , *fptr, *vptr;
        zl = o->ptr;
        //查询head指针
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if(fptr != NULL){
            //head不为空,说明ZipList不为空,开始查找key
            fptr = ziplistFind(zl, fptr, (unsigned char*) field, sdslen(field), 1);
            if (fptr != NULL) {
                /* Grab pointer to the value (fptr points to the field)
                抓取指向值的指针(fptr 指向字段)*/
                vptr = ziplistNext(zl, fptr);
                serverAssert(vptr != NULL);
                update = 1;

                /* Replace value
                替换掉值*/
                zl = ziplistReplace(zl, vptr, (unsigned char*)value,
                        sdslen(value));
            }
        }
        //创建新的而不是修改旧的value值
        if (!update) {
            /* Push new field/value pair onto the tail of the ziplist
            将新的字段/值对推送到 ziplist 的尾部*/
            zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
                    ZIPLIST_TAIL);
            zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
                    ZIPLIST_TAIL);
        }
        o->ptr = zl;

        /*再检查ziplist是否需要转换为哈希表 ,这里比较的是整个ziplist的长度,大于某个值则整个                  
        ziplist转换成hashtable存储*/
        if (hashTypeLength(o) > serverR.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    } else if (o->encoding == OBJ_ENCODING_HT) {
        //哈希编码, 直接添加到hash表或覆盖
        dictEntry *de = dictFind(o->ptr,field);
        if (de) {
            //把键值对中旧的value替换掉
            sdsfree(dictGetVal(de));
            if (flags & HASH_SET_TAKE_VALUE) {
                dictGetVal(de) = value;
                value = NULL;
            } else {
                dictGetVal(de) = sdsdup(value);
            }
            update = 1;
        } else { 
            //往hashTable中加入新的键值对
            sds f,v;
            if (flags & HASH_SET_TAKE_FIELD) {
                f = field;
                field = NULL;
            } else {
                f = sdsdup(field);
            }
            if (flags & HASH_SET_TAKE_VALUE) {
                v = value;
                value = NULL;
            } else {
                v = sdsdup(value);
            }
            dictAdd(o->ptr,f,v);
        }
    } else {
        serverPanic("Unknown hash encoding");
    }

    /* Free SDS strings we did not referenced elsewhere if the flags
     * want this function to be responsible. */
    if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field);
    if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
    return update;
}

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

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

相关文章

[Error] graphics.h: No such file or directory

原因&#xff1a;系统编译器找不到graphics.h文件&#xff0c;graphics.h这个文件就类似Java里面的jar包&#xff0c;也相当于Python里面pip安装的依赖包&#xff0c;graphics.h是样式设计文件&#xff0c;需要放到编译的文件夹里面 解决办法&#xff1a; 1.从该网址下载graphi…

Talk | 天津大学博士生赵煜:从平面图像中理解空间语义 - 视觉空间位置描述

本期为TechBeat人工智能社区第512期线上Talk&#xff01; 北京时间7月12日(周三)20:00&#xff0c; 天津大学博士生—赵煜的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “从平面图像中理解空间语义-视觉空间位置描述”&#xff0c;他与大家探讨了…

Html基础知识学习——兼容问题与解决方法(十六)

文章目录 1.计算一定要精确&#xff0c;不要让内容的宽高超出我们设置的宽高&#xff0c;在IE6下内容会撑开设置好的宽高2.元素浮动&#xff0c;宽度需要内容撑开&#xff0c;就给里面的块元素都加浮动3.在ie6.ie7下元素要浮动并在同一行 就给这些元素都加浮动4.注意标签嵌套规…

数学建模-典型相关分析

上节回顾 论文&#xff1a;常州大学一等奖淡水养殖… 要进行 pearson 相关系数 画散点图、折线图看是否相关检验正态分布满足上述&#xff0c;利用pearson相关系数 刚开始推导不会没关系&#xff0c;会应用就行&#xff0c;推导过程略&#xff0c;之后学习了后续知识&#xff…

JS基础教程

一、JS简介 1.1 学习内容 JavaScript&#xff08;简称“JS”&#xff09; 是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。它是作为开发Web页面的脚本语言而出名&#xff0c;JavaScript 基于原型编程、多范式的动态脚本语言&#xff0c;并且支持面向对…

复习之系统定时任务及延迟任务

一、延迟任务&#xff08;一次性的&#xff09; 1. 延迟任务的设定 at 时间 &#xff1a;具体时间设定延迟任务 设定成功后“ ctrl d "发起任务&#xff0c;" ctrl c " 取消。 at -l &#xff1a;查看延迟任务at -c 1 &#xff1a;查看序号为1 的延迟…

第四次CCF计算机软件能力认证

第一题&#xff1a;图像旋转 旋转是图像处理的基本操作&#xff0c;在这个问题中&#xff0c;你需要将一个图像逆时针旋转 90 度。 计算机中的图像表示可以用一个矩阵来表示&#xff0c;为了旋转一个图像&#xff0c;只需要将对应的矩阵旋转即可。 输入格式 输入的第一行包含两…

MySQL(二)索引原理以及优化

MySQL系列文章 MySQL&#xff08;一&#xff09;基本架构、SQL语句操作、试图 MySQL&#xff08;二&#xff09;索引原理以及优化 MySQL&#xff08;三&#xff09;SQL优化、Buffer pool、Change buffer MySQL&#xff08;四&#xff09;事务原理及分析 MySQL&#xff08;五&a…

滚珠螺杆的使用优势

滚珠螺杆主要是由螺杆、螺帽、钢珠、固定座、刮刷器以及回流管所构成的&#xff0c;根据循环系统的不同&#xff0c;还可以分为外循环式、内循环式、端塞循环式的滚珠螺杆。 滚珠螺杆发展至今&#xff0c;已经广泛应用到各产业机械的定位精度控制上&#xff0c;像精密工具机、产…

Karmada: Open, Multi-Cloud, Multi-Cluster Kubernetes Orchestration

Karmada是一个开源的多云应用编排和管理平台&#xff0c;旨在帮助用户在多个云提供商之间无缝地部署、编排和管理应用程序。 Karmada&#xff08;Kubernetes Armada&#xff09;是一个Kubernetes管理系统&#xff0c;它使您能够在多个Kubernetes集群和云环境中运行云原生应用程…

JavaFX 用户界面控件1——ChoiceBox ComboBox

1.选择框ChoiceBox JavaFX的ChoiceBox是一个用户界面控件&#xff0c;用于向用户显示一个选项列表&#xff0c;并允许用户从中选择一个或多个选项。下面是一个ChoiceBox的简单示例和使用介绍&#xff1a; 首先&#xff0c;导入JavaFX的相关类&#xff1a; import javafx.appl…

『表面』在平面模型上提取凸(凹)多边形

原始点云 直通滤波,z轴0~1.1 分割模型为平面&#xff0c;分割结果进行投影 提取多边形 代码: #include <pcl/ModelCoefficients.h> // 模型系数的数据结构&#xff0c;如平面、圆的系数 #include <pcl/io/pcd_io.h>#include <pcl/point_types.h> // 点云数据…

[SSM]Spring6基础

目录 一、Spring启示录 1.1OCP开闭原则 1.2DIP依赖倒置原则 1.3IoC控制反转 1.4DI依赖注入 二、Spring概述 2.1Spring简介 2.2Spring八大模块 2.3Spring特点 三、Spring的入门程序 3.1Spring的文件 3.2第一个Spring程序 3.3第一个Spring程序详细剖析 3.4Spring6启…

【LGR-145-Div.4】洛谷入门赛 #14(ABCDEI题解)

离开CSDN近五分之一坤年后&#xff0c;我又回归了&#xff0c;这段时间没刷题&#xff08;忙中考去了&#xff09;&#xff0c;于是乎参加了【LGR-145-Div.4】洛谷入门赛 #14&#xff0c;那才叫。。。&#xff08;这就是为什么没有FGH题解的原因&#xff09; T1 T352128 数字判…

【JavaEE】HTTPS及其安全机制

目录 1、什么是HTTPS 2、HTTPS的基本工作过程 2.1、使用对称密钥进行加密 2.2、使用非对称密钥进行加密 2.3、中间人攻击 2.4、证书 1、什么是HTTPS HTTPS是在HTTP协议的基础上引入了一个加密层&#xff08;SSL&#xff09;。HTTP协议内容都是按照文本的方式传输的&#x…

ChatGPT引领你掌握网站创建的秘诀!从0开始,轻松打造自己的个性化网站!

1 使用 HTML 生成一个完整的创业公司网站的落地页 prompt&#xff1a;Create a complete landing page for a start up company using HTML 生成整个网页的基础框架&#xff1a; 切换到WebStorm&#xff0c;将代码粘贴到新建的 HTML 文件。 接着右击浏览器打开 html 文件&am…

测试各个版本的飞鸽传书

测试各个版本的飞鸽传书 测试材料有windows系统的飞鸽传书有4个&#xff0c;linux系统的信使iptux&#xff08;类似飞鸽传书&#xff09;有2个&#xff0c;android系统的飞鸽传书有5个&#xff0c;都是以前下载保存在移动硬盘中&#xff0c;如今都找出来归类一起测试&#xff0…

124、仿真-基于51单片机智能电表系统设计(Proteus仿真+程序+原理图+配套资料等)

方案选择 单片机的选择 方案一&#xff1a;STM32系列单片机控制&#xff0c;该型号单片机为LQFP44封装&#xff0c;内部资源足够用于本次设计。STM32F103系列芯片最高工作频率可达72MHZ&#xff0c;在存储器的01等等待周期仿真时可达到1.25Mip/MHZ(Dhrystone2.1)。内部128k字节…

奇奇怪怪的知识点-EXCEL(1)

如何用Excel提取想要的数据 参考链接&#xff1a;Excel表格中如何在一串数字中提取几位数字 在日常工作中经常会用到EXCEL表格来进行数据的提取和处理&#xff0c;有时候很长一串数据我们只需要提取指定位数后面的数字&#xff0c;EXCEL中内置了很多丰富的函数可以帮助我们高…

图文讲解“延时双删”原因及必要性

目录 一、前言 二、常见更新策略 2.1 先删缓存&#xff0c;再更新数据库 2.2 先更新数据库&#xff0c;再删除缓存 2.3 普通双删 2.4 延迟双删 三、是否必要建议 一、前言 我们在实际项目中经常会使用到Redis缓存用来缓解数据库压力&#xff0c;但是当更新数据库时&…