Redis 源码解读之 Rehash 的调用时机

背景和问题

本文想要解决的问题

  1. 什么时机触发 Rehash 操作?
  2. 什么时机实际执行 Rehash 函数?

结论

  1. 什么时机触发 Rehash 操作?
  • 缩容: Redis 定时任务 serverCron 会在每个周期内检查 bucket 的使用情况。当存放 key 的数量和总 bucket 数的比例小于 HASHTABLE_MIN_FILL(10%),触发缩容 Rehash 操作。
  • 扩容:在每次调用 dictAddRaw 新增数据时,会检查 bucket 的使用比例。扩容的条件是以下之一:
    • dict_can_resize = 1 (该参数会在有 COW 操作的子进程运行时更新为 0,防止在子进程操作过程中触发 Rehash,导致内核进行大量的 Page 复制操作)
    • 当前存放的 key 的数量与 bucket 数量的比例超过了 dict_force_resize_ratio(5)
  1. 什么时机实际执行 Rehash 函数?
  • 定时任务: Redis 定时任务 serverCron 会在每个周期内执行 1ms 渐进式Rehash 操作。
  • 附着于其他操作:在 Redis 执行 dictAddRaw, dictGenericDelete, dictFind, dictGetSomeKeysdictGetRandomKey 等操作前会执行 Rehash 操作。

源码分析

dict 结构

dict 结构是 Redis 的主体,所有的用户数据都存在一个 dict 中。 dict 在整个 Redis 架构中的位置如下:

  • 一个 Redis 服务有 16 个 redisDb
  • 每个 redisDb 都维护着一个数据 dict (负责维护实际用户数据)和超时 dict(负责维护超时时间)。
  • dict 中维护着两个存数据的哈希表 dictht(维护两个dictht用于渐进式 Rehash 操作)。rehashidx 记录当前 Rehash 的状态。iterators 维护当前遍历 dict 的情况,类似于读锁。当该值大于 0 时,不能进行 Rehash 操作。(执行 dictScan 时操作会将该值加1)

渐进式 Rehash

dictRehash 实际执行 Rehash 操作,代码很简单。大概就是:将旧字典中某个 bucket 的冲突链表按照新的 Hash 规则插入新字典中。其中参数 n 指定本轮操作需要迁移旧字典 bucket

执行 Rehash 的时机

  • 定时任务
  1. 在 redis server 初始化时,会注册一个计时器事件, 定时执行 serverCron 任务。关于 redis 的事件循环机制,有机会单独开几篇博客来介绍。挖坑不填系列(不是)

  2. 定时任务 serverCron 的工作在源码中注释比较详细:触发过期 key 处理、监控服务运行状态、更新统计数据、渐进式 Rehash、触发 BGSAVE/AOF 及结束的子进程、处理客户端超时等等。

    当然咱们这里需要关系的是渐进式 Rehash,serverCron 通过调用 databasesCron 函数来实现。至于其他内容,有机会单独开几篇博客来介绍。挖坑不填系列+1(不是)

  3. 若没有子进程进行备份操作, databasesCron 会一次检查每个 DB 的表,是否需要 Rehash(见上一小节)。如果存在需要 Rehash 或正在 Rehash 的 DB,则通过 incrementallyRehash 对其进行 Rehash。一次触发仅执行一次(成功的)渐进式 Rehash 操作。

  4. incrementallyRehash 分别对数据/超时时间字典进行最长 1ms 的 Rehash 操作。该函数如果实际执行了 Rehash 操作,会返回 1。

  5. dictRehashMilliseconds 每次执行 100 次渐进式 Rehash,持续执行 ms ms。

  • 附着于其他操作

  • _dictRehashStep: 在 dict 执行操作过程中会调用 _dictRehashStep 函数执行一轮 Rehash 操作。

  • dictAddRaw: 该函数在执行数据插入操作前,会调用 _dictRehashStep 执行一轮 Rehash 操作。

  • dictGenericDelete: 该函数在执行物理/逻辑删除数据前,会调用 _dictRehashStep 执行一轮 Rehash 操作。

  • dictFind: 该函数在执行查询数据操作前,会调用 _dictRehashStep 执行一轮 Rehash 操作。

  • dictGetSomeKeys/dictGetRandomKey: 在数据逐出/过期操作时,会调用 dictGetSomeKeys/dictGetRandomKey 函数获取一些需要操作的 key。这两个函数在获取 key 之前会执行 Rehash 操作。

触发 Rehash 的时机

dictExpand 函数根据当前 dict 存放的数据量,触发 Rehash 操作并设置相关参数:将 bucket 的数量扩大/缩小到 _dictNextPower(dict.size)

  • 扩容: 在每次新增 key 的时候,会尝试触发扩大 bucket 数。


可以看到,扩容的条件是以下之一:

  • dict_can_resize = 1 (该参数会在有 COW 操作的子进程运行时更新为 0,防止在子进程操作过程中触发 Rehash,导致内核进行大量的 Page 复制操作)
  • 当前存放的 key 的数量与 bucket 数量的比例超过了 dict_force_resize_ratio(5)
  • 缩容:定时任务 serverCron 在每个周期会尝试减少 bucket 的数量。

    通过源码可以知道,当存放的 key 的数量小于 bucket 数的 10% 时,会触发缩容 Rehash。

参考文献

  • redis/src - github.com
  • graphviz.org
  • oxygen - Help you easily generate hand-drawn style diagrams.

Redis 源码解读之 Rehash 的调用时机相关推荐

  1. redis源码解读二

    上一篇解读了一下SDS,本来觉得完了,但之后想想感觉少点什么,现在我们从使用的角度去梳理一下,大家想想对于字符串, 我们经常使用的有哪几个方法呢?这些方法又是怎么实现的? 在研究上面的几个方法之前我们 ...

  2. redis源码剖析(3):基础数据结构dict

    目录 1.dict概述 2.字典的定义 3.哈希算法 4.字典的初始化及新增键值对 4.1 字典初始化 4.2 新增键值对 5.rehash(重新散列)操作 5.1 rehash操作方式 5.2 re ...

  3. PyTorch 源码解读之 cpp_extension:讲解 C++/CUDA 算子实现和调用全流程

    "Python 用户友好却运行效率低","C++ 运行效率较高,但实现一个功能代码量会远大于 Python".平常学习工作中你是否常听到类似的说法?在 Pyth ...

  4. Redis 源码该怎么读?(译文)

    注:本博客由「柏油」翻译自 https://github.com/redis/redis/tree/6.2,转载请注明出处! 译文如下: Redis internals (内部结构) 当你阅读 REA ...

  5. Redis源码解析——字典基本操作

    有了<Redis源码解析--字典结构>的基础,我们便可以对dict的实现进行展开分析.(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的, ...

  6. Redis源码:朴实无华且枯燥

    前两天,一哥们去面后端,起初 SQL 优化技巧.分布式架构.中间件都答得都挺好,没想到最后折在了 Redis 上,回来跟我复盘了一波: 面试官:"Redis 什么时候做 Rehash ?&q ...

  7. redis源码dict.c simple reading

    2019独角兽企业重金招聘Python工程师标准>>> 0. precondition 首先,你需要知道Redis的六种数据结构,包括list, set, zset, string, ...

  8. Redis源码阅读,从入门到放弃

    作为后端工程师,我们在面试和工作中都会用到 Redis,特别是大型互联网公司面试时,不仅要求面试者能简单使用 Redis,还要求懂 Redis 源码层面的实现原理,具备解决常见问题的能力.可以说,熟练 ...

  9. 01 Redis源码起航

    源码解读 Redis 源码文件分布大致如下: redisDb结构介绍 首先看下redisDb的结构,在server.h文件中: typedef struct redisDb {dict *dict; ...

最新文章

  1. php一个英文几个字符,PHP指定截取字符串中的中英文或数字字符的实例分享
  2. ffmpeg入门及java操作ffmpeg对视频进行处理
  3. UML模型中的图-行为图【交互图-序列图、协作图】
  4. 操作数数据类型 char 对于 sum 运算符无效。_数据类型和运算符
  5. neo4j python 算法_图论与图学习(二):图算法
  6. HTML5中的audio在手机端和微信端的不能自动播放
  7. 【阿里云 CDP 公开课】 第二讲:CDH/HDP 何去何从
  8. Conda solving environment一晚上还不能完成有解吗?
  9. 财务管理与计算机论文,计算机小论文--浅论计算机与财务管理.doc
  10. 纽泰格深交所上市:市值52亿 扣非净利下降52%
  11. php加载COM组件失败原因及其解决方法
  12. Codeforces 39H - Multiplication Table(进制转换)
  13. 消融实验(Ablation study)的最佳解释
  14. 2021春节红包活动平台大全 春节平台集卡活动汇总
  15. 深度学习之文本生成图片
  16. 硬件工程师成长之路(11)——职业规划
  17. LeetCode(跳跃游戏)
  18. python endswith函数_python endswith和startwith
  19. C#编程:用Substring获取年份生肖-4
  20. Python教程:异或运算符(^)、与运算符()、或运算符(|)、反运算符(~)、右移运算符(>>)、无符号右移运算符(>>>)

热门文章

  1. 光纤收发器的六个指示灯代表是什么意思?
  2. 俄勒冈大学计算机科学专业,2020年俄勒冈大学排名TFE Times美国最佳计算机科学硕士专业排名第68...
  3. ffmpeg framework
  4. JAVA_获取正常上班工作日-除去节假日_双休日_加上加班
  5. 为什么我们要掌握Linux系统编程?
  6. 小波阈值去噪的原理及程序
  7. 要命啦!Word中快速录入大全,内含快捷键小技巧,快来一起学习!
  8. Coursera登不上的全套方法
  9. [RFC6023] 互联网密钥交换版本 2 (IKEv2) 安全协会 (SA) 的Childless 初始化 (翻译)
  10. day1 704.二分查找 27.移除元素