Redis从精通到入门——数据类型Hash实现源码详解

Hash简介

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

  • 优点
    • 同类数据归类整合储存,方便数据管理
    • 相比使用多个string操作消耗内存与cpu更小
    • 相比使用多个string储存更节省空间
    • 相比使用单个String转JSON存储,支持局部值的更新、查询
  • 缺点
    • 过期功能不能使用在field上,只能用在key上

Hash数据大小和元素数量阈值都没有超过设置的值时,此时底层使用 ziplist 去实现,那么会有一个有意思的场景:此时的数据存储是有序的按照插入顺序排序,且对已存在的Key修改其Value也不会影响数据顺序。这一点和JAVA有着较大的差距大家需要注意。
想了解ziplist可查阅历史博客,传输门:Redis从精通到入门——数据类型List实现源码详解
在这里插入图片描述

Hash的常用操作

  • HSET key field value [field value …]:将一个或多个field/value插入到哈希表中;
  • HGET key field :返回key中指定 field 的 value 值;
  • HMGET key field [field …] :批量返回key中指定field 的 value 值;
  • HKEYS key :返回哈希表 key 中的所有field;
  • HGETALL key :返回哈希表 key 中,所有的field和value;
  • HVALS key :返回哈希表 key 中的所有value;
  • HEXISTS key field :查看哈希表 key 中,field 是否存在;
  • HDEL key field [field …] :删除哈希表 key 中的一个或多个field ;
  • HINCRBY key field increment :为哈希表 key 中 field 的值加上增量 increment类似String的INCRBY,同时也支持传递负数。此命令在使用Rrdis Hash实现购物车的场景下非常好用
  • HLEN key :返回哈希表 key 中field的数量;
  • HSETNX key field value :将哈希表 key 中的域 field 的值设置为 value 仅当 field 不存在时才会set成功

应用场景

  • 购物车
  • 对象信息缓存希望大家不要再去把对象转JSON当String存进去了

Hash实现

Hash 数据结构底层实现为一个字典( dict ),也是RedisDB用来存储K-V的数据结构,当数据量比较小,或者单个元素比较小时,底层用 ziplist 存储,数据大小和元素数量阈值可以通过如下参数设置:

  • hash-max-ziplist-entries 512:ziplist 元素个数超过 指定个数时默认512个,将由ziplist 改为 hashtable 编码 ;
  • hash-max-ziplist-value 64: 当ziplist 中单个元素大小超过 指定字节数默认64byte,将由ziplist 改为 hashtable 编码 ;

Hash—ziplist实现

Hash 的底层实现是 ziplist 时, ziplist 中的 entry 的个数永远是双数,是以 key value key value … key value 形式存储。进行hget 操作时则不需要进行 hashCode 的计算,通过直接遍历ziplist,比对key的值来获取value。

此处不再额外对ziplist源码进行阅读,如需了解请通过文章上部分传送门查看ziplist具体实现。

图解Hash—ziplist实现

在这里插入图片描述

Hash—字典dict实现

    dict (dictionary 字典),通常的存储结构是Key-Value形式的,通过Hash函数对key求Hash值来确定Value的位置,因此也叫Hash表,是一种用来解决算法中查找问题的数据结构,默认的算法复杂度接近O(1)。
    字典在Redis中的作用是非常巨大的,对Redis数据库的增删改查等操作都构建在对字典的操作之上,因此,了解字典的底层实现能让我们对Redis有更深的理解。

其实哪怕我们常规的get、set 都是基于dict字典实现的,Hash这里只是复用了Redis的字典实现。

源码阅读

咱们主要关注的数据结构有如下三个:

  • dict:是Redis中的字典结构,包含两个dictht;
  • dictht:表示一个Hash表,包含一个或多个dictEntry;
  • dictEntry: 表示一个Key-Value节点;
/*
 * dict 字典
 * 大家需要关注的是dictht ht[2]:
 * 这里设计存储两个dictht 的指针是用于Redis的rehash,后文中进行详解
 */
typedef struct dict {
    dictType *type;			/*类型特定函数*/
    void *privdata;			/*私有数据*/
    dictht ht[2];			/*用于存储数据的两个hash表,正常只有一个hash表中有数据,只有在rehash的过程中才会出现两个hash表同时存在数据*/
    long rehashidx; 		/*rehash目前进度,当哈希表进行rehash的时候用到,其他情况下为-1*/
    unsigned long iterators; /*迭代器数量*/
} dict;

/* 
 * 这是我们的哈希表结构。 每个字典都有两个
 * 一个哈希表里面有多个哈希表节点(dictEntry),每个节点表示字典的一个键值对
 */
typedef struct dictht {
    dictEntry **table;		/*哈希表数组指针*/
    unsigned long size; 	/*hashtable 容量 数组大小*/
    unsigned long sizemask;	/*size -1*/
    unsigned long used;		/*hashtable中元素个数,正常情况下当used/size=1时将进行扩容操作*/
} dictht;

/* 
 * 哈希表节点
 */
typedef struct dictEntry {
    void *key;
    union {
        void *val;		/*指向Value值的指针,正常是指向一个redisObject*/
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;	/*当出现hash冲突时使用链表形式保存hashcode相等但是field 不相等的数据,这里就是指向下一条数据的指针*/
} dictEntry;

图解dict

由于dict字典并非只在Redis 的 hash数据结构中使用,所以下面的图解会画的尽量详细些
在这里插入图片描述

渐进式rehash

Redis 的字典可以看做 JAVA的Map 为了避免链表过长都需要进行hashTable的扩容操作,redis默认正常情况下扩容的负载因子 = 1。
负载因子 = used / size。每次扩容后hashTable的大小 = size * 2也就是成倍扩容

  • Redis rehash流程如下:
  • 为ht[1]分配空间,此时字典同时持有ht[0]和ht[1];
  • 将rehashidx设为0,表示rehash正式开始;
  • 在rehash期间,每次对字典执行任意操作时,程序除了执行对应操作之外,还会顺带将ht[0]在rehashidx索引上的所有键值对rehash到ht[1],操作完后将rehashidx的值 + 1;
  • Redis本身也会有事件轮询,哪怕没有命令访问,也会通过轮询事件逐渐完成数据迁移;
  • 当rehashidx的值增加到 = ht[0].size,此时ht[0]的所有键值对都已经迁移到ht[1]了。程序会把ht[1]设置为ht[0],并重新在ht[1]上新建一个空表。将rehashidx重新置为-1,以此表示rehash完成。

Redis为什么需要渐进式rehash?

哪怕Redis 6.0支持的客户端操作的多线程,但是真正去执行操作的还是只有一个主线程,所以当存在超大的hashTable进行扩容时,如果不去渐进式扩容,单次扩容时间太长且扩容期间Redis服务不可用,将导致大量的线程等待。

你的点赞就是我创作的最大动力,如果写的不错,来个三连行不行

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhibo_lv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值