Redis 的哈希 hash是什么?

Redis 的哈希 hash是什么?

重要内容

在 Redis 中,哈希(Hash)是一种键值对的集合,其中每个键 对应的值是多对字段 - 值(field - value)的映射

如:Key → { Field1: Value1, Field2: Value2, ... }

  • 内部表示 :哈希本质上是一个二维的数据结构,一个键对应多个字段,每个字段关联一个值
  • 操作特性:可以独立地对每个字段进行操作,而不需要对整个哈希进行操作。这使得哈希非常适合存储对象,因为对象的每个属性可以作为一个字段

扩展知识

Hash 常用命令

命令 简述 使用
HSET 设置哈希表中字段的值 HSET key field value
HGET 获取字段值 HGET key field
HGETALL 获取所有字段和值 HGETALL key
HDEL 删除指定字段 HDEL key field

Hash 底层实现解析

Hash是一种数据基础数据结构,类似于数据结构中的哈希表,一个哈希可以存储2的32次方-1个键值对

底层结构需要分成两个情况

  • Redis6及之前,Hash的底层是压缩列表加上哈希表的数据结构(ziplist+hashtable)
  • Redis7之后,Hash的底层是紧凑列表加上哈希表的数据结构(Listpack+hashtable)

压缩列表和紧凑列表的比较

两者时间复杂度都是O(n),其主要区别就在于 listpack 解决了 ziplist 的级联更新问题

紧凑列表、压缩列表 与 哈希表转换的条件

哈希键的字段个数(默认512)以及每个字段名和字段值的长度(默认64)

注意:在使用hashtable结构之后,就不会再退化成ziplist或listpack,之后都是使用hashtable进行存储

Hashtable 底层数据结构

Redis 的 hashtable 通过字典(dict)实现,字典包含两个哈希表(dictht)和渐进式 rehash 机制

字典的结构

c 复制代码
typedef struct dict {
    dictType *type;        // 类型特定函数(哈希计算、键值复制等)
    void *privdata;        // 私有数据
    dictht ht[2];          // 两个哈希表(用于渐进式 rehash)
    long rehashidx;        // rehash 进度索引(-1 表示未进行)
    unsigned long iterators; // 当前运行的迭代器数量
} dict;

哈希表的结构

c 复制代码
typedef struct dictht {
    //哈希表数组
    dictEntry **table;
    //哈希表大小
    unsigned long size;  
    //哈希表大小掩码,用于计算索引值
    unsigned long sizemask;
    //该哈希表已有的节点数量
    unsigned long used;
} dictht;

哈希节点的结构

c 复制代码
typedef struct dictEntry {
    //键值对中的键
    void *key;
  
    //键值对中的值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
          	
    //指向下一个哈希表节点,形成链表
    struct dictEntry *next;
} dictEntry;

结构图

通过图片能够完整的展示 hashtable 内部的数据结构

哈希表的核心实现机制

哈希函数与索引计算
  • 哈希函数 :Redis 使用 MurmurHash2 算法(非加密型哈希)将键(key)转换为 64 位或 32 位哈希值

  • 索引计算

    c 复制代码
    index = hash & d->ht[table].sizemask; // 等价于 hash % size

    通过位掩码(sizemask)将哈希值映射到哈希表数组的索引位置

  • 示例:假设哈希表的大小为 8,哈希值为 101

    • 位掩码为 7(即 8−1)
    • 计算索引:index = 101 & 7 = 5
    • 因此,键 101 被映射到索引 5
负载因子与扩容/缩容
  • 负载因子 : used(已用节点数)/ size(桶总数)
  • 扩容触发条件
    • 负载因子 >= 1,这个时候说明空间非常紧张,新数据是在哈希节点的链表上找到的,这个时候如果服务器没有执行 RDB快照或者AOF重写这两个持久化机制的时候,就会进行rehash操作
    • 当负载因子 >= 5,这个时候说明哈希冲突非常严重了,这个时候无论有没有进行AOF重写或者RDB快照,都会强制执行rehash操作
  • 缩容触发条件
    • used < size / 10(负载因子 < 0.1)时,缩容至 used 最近的 2 的幂

渐进式 Rehash 机制

为避免一次性迁移大量键值对导致服务阻塞,Redis 采用 渐进式 rehash,将迁移分摊到多次操作中

Rehash 流程

  1. 准备阶段
    • 分配新哈希表 ht[1],大小为满足扩容/缩容条件的 2 的幂
    • 设置 rehashidx = 0,表示开始从索引 0 迁移数据
  2. 迁移阶段
    • 每次操作触发迁移 :在增删改查操作中,每次迁移 ht[0].table[rehashidx] 桶内的所有节点到 ht[1]
    • 更新索引rehashidx++,直至所有桶迁移完成
  3. 完成阶段
    • 释放 ht[0] 的内存,将 ht[1] 设置为 ht[0]
    • 重置 ht[1] 为空哈希表,设置 rehashidx = -1
相关推荐
xcLeigh8 分钟前
安装教程:windows上安装oracle详细教程
数据库·ide·oracle·可视化工具
TPCloud1 小时前
如何快速解决django存储session变量时出现的django.db.utils.DatabaseError错误
数据库·python·django
山外有山a1 小时前
neo4j知识图谱常用命令
服务器·数据库·oracle
MXsoft6181 小时前
监控易一体化运维:监控易机房管理,打造高效智能机房
大数据·数据库
demonlg01122 小时前
Go 语言标准库中database模块详细功能介绍与示例
开发语言·数据库·golang
Andya_net2 小时前
Redis | 基于 Redis 实现机器列表 Token 缓存的 Java 实现
java·redis·缓存
故城、2 小时前
redis使用
java·redis
Chandler242 小时前
Redis:String 类型 内部实现、编码、命令及应用场景
数据库·redis·缓存
敲键盘的小夜猫2 小时前
Redis GEO 命令详解:轻松实现“附近的人“功能
java·redis
Jtti2 小时前
ubuntu服务器进程启动失败的原因分析
服务器·数据库·ubuntu