当前位置: 首页 > news >正文

Redis哈希表Rehash全解析:扩容缩容背后的渐进式智慧

引言

作为Redis核心数据结构之一的哈希表(dict),其性能直接影响着Redis的整体表现。当数据量频繁增减时,哈希表需要动态调整容量以保持高效——这就是传说中的Rehash(重新哈希)。今天我们就来扒一扒Redis哈希表Rehash的底层逻辑,尤其是扩容/缩容时的渐进式迁移机制。


一、为什么需要Rehash?触发条件大揭秘

哈希表的核心矛盾是:空间利用率查询效率的平衡。当哈希表中元素太多或太少时,要么冲突激增(查询变慢),要么内存浪费(性价比低)。Redis通过**负载因子(Load Factor)**来量化这一矛盾,并以此触发Rehash。

负载因子怎么算?

负载因子 = 已存储键值对数量(size) / 哈希表桶的数量(table_size)。

触发Rehash的两种场景:

  1. 扩容触发(空间不够用):
    当负载因子 > 1 时(正常情况),或 Redis 正在执行 BGSAVE/BGREWRITEAOF 持久化时(负载因子 > 5)。
    原因:数据量超过桶数量,哈希冲突概率飙升(链式冲突链变长),查询时间复杂度可能从O(1)退化为O(n)。

  2. 缩容触发(内存浪费):
    当负载因子 < 0.1 时。
    原因:桶数量远大于实际数据量,大量空间被闲置,内存利用率低下。


二、Rehash的“双保险”:双哈希表设计

如果直接让原哈希表“暴力”扩容,所有数据需要重新计算哈希并插入新桶,这在数据量大时会导致Redis阻塞(主线程被长时间占用)。为了不影响线上服务,Redis设计了双哈希表结构:

typedef struct dict {dictType *type;          // 类型方法集合(哈希函数、比较函数等)void *privdata;          // 私有数据(如哈希种子)dictht ht[2];            // 核心:两个哈希表(ht[0]和ht[1])long rehashidx;          // Rehash进度(-1表示未进行)int16_t pauserehash;     // Rehash是否暂停(持久化时可能暂停)
} dict;
  • ht[0]:主哈希表,日常存储数据。
  • ht[1]:临时哈希表,仅在Rehash时使用,用于承接ht[0]迁移的数据。

Rehash的本质,就是把ht[0]的数据“搬家”到ht[1],完成后交换两者的角色(ht[0]指向原ht[1],ht[1]清空)。


三、渐进式Rehash:边干活边搬家,不阻塞服务

如果一次性迁移所有数据(比如ht[0]有100万键),主线程会被卡住很久,这对高并发场景是不可接受的。Redis的解决方案是渐进式Rehash——将迁移过程拆成无数个小步骤,每次操作(查询、插入、删除)时“顺手”迁移一点数据。

渐进式Rehash的具体流程:

1. 初始化阶段:启动Rehash

当触发Rehash条件时,Redis会:

  • 为ht[1]分配新内存(扩容时是ht[0].table_size×2,缩容时是ht[0].table_size/2,且必须是2的幂次方)。
  • 设置rehashidx=0(表示从ht[0]的第0号桶开始迁移)。
  • pauserehash设为0(允许其他操作参与迁移)。
2. 日常操作:捎带脚迁移数据

Rehash期间,每次对Redis执行增删改查操作时,都会触发一次“小迁移”:

  • 查询操作:先查ht[0]的当前桶(如果该桶已被迁移,就查ht[1]的同索引桶)。
  • 插入/更新操作:只往ht[1]写数据(避免修改正在迁移的ht[0])。
  • 删除操作:同时查ht[0]和ht[1],删除对应键(防止数据残留)。

每次操作后,rehashidx递增1,直到rehashidx等于ht[0].table_size(所有桶迁移完成)。

3. 收尾阶段:交换角色,释放旧表

当所有桶迁移完成后:

  • ht[0]和ht[1]交换身份(ht[0]指向原ht[1],ht[1]清空)。
  • 释放原ht[0]的内存(如果是缩容,可能保留小内存块复用)。
  • rehashidx重置为-1,Rehash结束。

四、关键细节:那些你可能忽略的设计巧思

1. 为什么桶的数量必须是2的幂次方?

哈希表的索引计算依赖哈希值与桶数量的取模运算。如果桶数是2的幂次方(如8、16、32),可以用位运算(hash & (table_size - 1))代替取模(hash % table_size),效率更高。扩容/缩容时,桶数翻倍或减半,索引计算依然高效。

2. 缩容也有底线:最小容量限制

为了防止频繁缩扩容,Redis规定缩容后的桶数至少为DICT_HT_INITIAL_SIZE(默认4)。即使负载因子低至0.05,也不会缩到比4更小。

3. 持久化期间的特殊策略

执行BGSAVEBGREWRITEAOF时,Redis会将扩容的负载因子阈值从1提高到5。这是因为持久化是磁盘IO密集型操作,此时如果触发扩容,可能导致主线程和持久化线程竞争CPU资源,影响性能。


总结:Rehash是Redis的“动态平衡术”

Redis的哈希表Rehash机制,通过双哈希表避免了数据迁移时的服务中断,通过渐进式迁移将大任务拆解为无数小步骤,确保了高并发场景下的稳定性。无论是扩容时的“空间换时间”,还是缩容时的“时间换空间”,本质上都是Redis对性能与内存利用率的精准权衡。

下次使用Redis时,不妨想想:当你执行SET key value时,可能正悄悄参与着一场“数据搬家”的大工程——这或许就是Redis“高性能”背后的浪漫吧! 😊

(注:文中涉及的源码解析基于Redis 7.0版本,不同版本可能有细微差异。)

http://www.lryc.cn/news/574691.html

相关文章:

  • 一种集成统计、视觉和基于规则方法的新型可解释医学图像分类人工智能框架|文献速递-最新论文分享
  • ffmpeg下载地址
  • wpf单文件打包还有 一些dll打包不进去?
  • 基于单片机的语音控制设计(论文)
  • PYTHON从入门到实践2-环境配置与字符串打印用法
  • 【开源项目】比 PyInstaller 更方便:图形界面打包 Python 脚本的体验
  • linux nginx更换域名证书
  • Ubuntu服务器中MySQL如何进行主从复制
  • 解锁阿里云AnalyticDB:数据仓库的革新利器
  • 支持向量机(SVM)python语言版本
  • 从0开始学习R语言--Day31--概率图模型
  • FPGA基础 -- Verilog 验证平台之 **cocotb 验证 `阶乘计算模块(factorial)` 的例子**
  • 洛谷P1092 [NOIP 2004 提高组] 虫食算
  • 基于DE1-SoC的My_First_oneAPI(一)
  • SpringBoot 3.0 - 自定义注解+拦截器+Redis 解决接口幂等性
  • 【apache-maven3.9安装与配置】
  • 从虚拟机角度解释python3相对导入问题(下)
  • 轻量化实物建模革命:WebGL如何实现复杂模型的高效加载与交互
  • ​CentOS 7 单用户模式重置 root 密码完整指南
  • 新中国风通用读书颂词分享PPT模版
  • JS核心操作符:从基础到ES6+
  • (ICML-2023)BLIP-2:使用冻结图像编码器与大型语言模型的语言-图像预训练引导方法
  • SQL Server 查询数据库及数据文件大小
  • 使用 spark-submit 运行依赖第三方库的 Python 文件
  • RGB相机 vs 灰度相机
  • Apache Flink Kafka 写连接器源码深度剖析
  • java-SpringBoot框架开发计算器网页端编程练习项目【web版】
  • Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights
  • DataSophon 1.2.1集成Flink 1.20并增加JMX 监控
  • pyqt setContentsMargins