redis中hash数据结构及说明

 更新时间:2023年01月18日 15:08:35   作者:酒剑随马@  
这篇文章主要介绍了redis中hash数据结构及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

hash的数据结构

  • hash底层数据结构的实现包括两种:ziplist和字典当
  • 保存的所有键值对字符串长度小于 64 字节并且键值对数量小于 512 时使用ziplist ,否则使用字典的方式

ziplist底层实现

ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。

他能在O(1)的时间复杂度下完成list两端的push和pop操作。

但是因为每次修改操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关

ziplist 中包含有zlbytes、zltail、zllen、entry、zlend等属性

  • zlbytes:表示整个ziplist所占的空间大小,占4个字节
  • zltail:压缩列表尾元素相对于压缩列表起始地址的偏移量,占4个字节
  • zllen:压缩列表的元素数目,占两个字节;那么当压缩列表的元素数目超过(2^16)-1怎么处理呢?此时通过zllen字段无法获得压缩列表的元素数目,必须遍历整个压缩列表才能获取到元素数目
  • zlend:压缩列表的结尾,占一个字节,恒为0xFF(255)
  • entry:压缩列表存储的元素,可以为字节数组或者整数

entry 中包含有previous_entry_length、encoding、content等属性

  • previous_entry_length:记录前一个节点的长度

该属性根据前一个节点的大小不同可以是1个字节或者5个字节;如果数值小于254,那就只用一个字节来表示长度,如果长度大于等于254就用5个字节,第一个字节是固定值254(FE)来标识这是个特殊的数据,剩下的4个字节来表示实际的长度

为什么要这么设计?

压缩列表目的是为了尽可能的节省存储空间,数据进行紧邻存储在一块连续的内存区域中;压缩表中元素的长度的是不同的,且为了减少存储空间,并没有保存前后节点的指针,无法通过后退指针来找到上一个元素,而通过保存上一个节点的长度,用当前的地址减去这个长度,就可以很容易的获取到了上一个节点的位置,通过一个一个节点向前回溯,来达到从表尾往表头遍历的操作

  • encoding:数据的编码形式(字符串还是数字,长度是多少)
  • content:实际存储的数据

修改操作耗费性能:ziplist在内存中是高度紧凑的连续存储,这意味着它对修改并不友好,如果要对ziplist做修改类的操作,那就需重新分配新的内存来存储新的ziplist,代价很大

ziplist其实是一个逻辑上的双向链表,可以快速找到头节点和尾节点,然后每个节点(entry)中也包含指向前/后节点的"指针",但作者为了将内存节省到极致,摒弃了传统的链表设计(前后指针需要16字节的空间,而且会导致内存碎片化严重),设计出了内存非常紧凑的存储格式。

内存是省下来了,但操作复杂性也更新的复杂度上来了

字典

底层实现

字典(dict):其中包含长度为2的哈希表数组dictht,rehashIdx(默认-1)如果为-1说明当前没有扩容,如果不为 -1 则表示正在进行扩容,记录了原hash表需rehash的数组下标

hash表数组中,ht[0] 在第一次往字典中添加键值时分配内存空间,而另一个 ht[1] 将会在hash表中数组扩容/缩容才会进行空间分配

hash表(dictht):字典dictht数组元素,其中包括了

1 数据 dictEntry 类型的数组,每个数组的 item 可能都指向一个链表

2 数组长度 size

3 sizemask 等于 size - 1

4 当前 dictEntry 数组中包含总共多少节点

hash表数组中元素(dictEntry):真正的数据节点,包括 key、value 和 next 节点

整体结构如下所示:

扩容

扩容时机:在dict->rehashidx == -1 , 也就是字典没有正在进行扩容/缩容的前提下,以下三种情况下对哈希表进行扩容并标记 dict->rehashidx 字段为0,且扩展的哈希表的数组大小是第一个hash表长度的 2倍

  • 字典已使用节点数和数组大小之间的比率至少为 1:1,并且 dict_can_resize 为true
  • 已使用节点数和字典大小之间的比率超过 dict_force_resize_ratio,该值默认为5
  • 哈希表刚初始化完,是个空表,给哈希数组设置默认大小 DICT_HT_INITIAL_SIZE (4)

扩容方式:为ht[1]分配长度为ht[0]2倍长度,rehashIdx设置为0表示正在进行扩容rehash,采取渐进式rehash

redis对一个字典的rehash操作,不是一次性把该字典 dict->ht[0] 哈希表上所有哈希数组里的哈希数组元素全部重新哈希到 dict->ht[1]

而是将全部的rehash操作分散到对该字典操作的各个命令上了,每次进行"一步"哈希操作(增加k/v,删除k/v,查找key,随机返回key)

  • 下标从dict->rehashidx开始,在 dict->ht[0].table 数组中找到第一个不为NULL的项
  • 将该项链表上的所有元素全部hash映射到 ditct->ht[1] 上
  • 每重新映射一个元素, dict->ht[0].used --, dict->ht[1].used ++
  • 该项链表处理完后,将 dict->rehashidx ++

如果 dict->ht[0].used == 0,说明 dict->ht[0]中的元素已全部rehash到dict->ht[1],释放dict->ht[0].table 数组,设置 dict->ht[0] = dict->ht[1],重置 dict->ht[1]的字段,设置 dict->rehashidx = -1,rehash操作结束

缩容

字典有扩容也有缩容,从字典中删除key后,若字典中的元素个数与字典数组大小满足一定关系,会触发缩容操作,缩绒条件是:

哈希数组长度大于默认值DICT_HT_INITIAL_SIZE (4),且节点数量 与 字典哈希表数字大小的比例 小于10%

缩容后新数组长度为hash表中元素个数

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 如何在SpringBoot中使用Redis实现分布式锁

    如何在SpringBoot中使用Redis实现分布式锁

    这篇文章主要介绍了如何在SpringBoot中使用Redis实现分布式锁,在实际开发中有可能会遇到多个线程同时访问同一个共享变量,那么上锁就很重要了,需要的朋友可以参考下
    2023-03-03
  • 查看redis占用内存的实现方法

    查看redis占用内存的实现方法

    这篇文章主要介绍了查看redis占用内存的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Redis分布式锁之红锁的实现

    Redis分布式锁之红锁的实现

    本文主要介绍了Redis分布式锁之红锁的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Redis源码解析sds字符串实现示例

    Redis源码解析sds字符串实现示例

    这篇文章主要为大家介绍了Redis源码解析sds字符串实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • RedisDesktopManager远程连接redis的实现

    RedisDesktopManager远程连接redis的实现

    本文主要介绍了RedisDesktopManager远程连接redis的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • 利用控制台如何对Redis执行增删改查命令

    利用控制台如何对Redis执行增删改查命令

    这篇文章主要给大家介绍了关于利用控制台如何对Redis执行增删改查命令的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-08-08
  • Java实现多级缓存的方法详解

    Java实现多级缓存的方法详解

    对于高并发系统来说,有三个重要的机制来保障其高效运行,它们分别是:缓存、限流和熔断,所以本文就来和大家探讨一下多级缓存的实现方法,希望对大家有所帮助
    2024-02-02
  • 如何自定义redis工具jar包供其他SpringBoot项目直接使用

    如何自定义redis工具jar包供其他SpringBoot项目直接使用

    这篇文章主要介绍了如何自定义redis工具jar包供其他SpringBoot项目直接使用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Redis核心原理与实践之字符串实现原理

    Redis核心原理与实践之字符串实现原理

    这本书深入地分析了Redis常用特性的内部机制与实现方式,内容源自对Redis源码的分析,并从中总结出设计思路、实现原理。对Redis字符串实现原理相关知识感兴趣的朋友一起看看吧
    2021-09-09
  • 浅谈一下Redis的缓存穿透、击穿和雪崩

    浅谈一下Redis的缓存穿透、击穿和雪崩

    这篇文章主要介绍了浅谈一下Redis缓存穿透、击穿和雪崩,缓存穿透是指在使用缓存系统时,频繁查询一个不存在于缓存中的数据,导致这个查询每次都要通过缓存层去查询数据源,无法从缓存中获得结果,需要的朋友可以参考下
    2023-08-08

最新评论