redis的渐进式rehash机制

简述

在redis的字典(dict.h)实现中,当哈希表保存的键值对太多或者太少时,会触发扩展/收缩;

  • 触发收缩:负载因子小于 0.1
  • 触发扩展:以下任一条件符合即可
    • 服务器目前没有在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 1
    • 服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 5
# 负载因子 = 哈希表已保存节点数量 / 哈希表大小
load_factor = ht[0].used / ht[0].size

由于存在表中的键值对可能有成百上千个,一次性rehash到ht[1]的话会导致服务器在一段时间内停止服务,于是出现了渐进式rehash,这个动作并不是一次性的,而是分多次,渐进式完成的

机制

  • 为ht[1]分配空间,此时字典会持有ht[0]和ht[1]两个hash表(rehash期间的内存变化如下,可以看到rehash后内存有了较大的上升,当rehash结束后会释放ht[0]的内存,则内存占用会下降;注意:若是扩展前,本机内存不够了,则会触发满容驱逐淘汰)

redis的渐进式rehash机制_第1张图片

  • 将dict结构体中的rehashidx置为0,表示rehash开始
/* 查看字典是否正在 rehash */
#define dictIsRehashing(ht) ((ht)->rehashidx != -1)

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    // rehash 索引,使用宏dictIsRehashing判断是否在rehash
    // 当 rehash 不在进行时,值为 -1
    // _dictInit置为-1, dictExpand时置为0,dictRehash中当ht[0].used==0即rehash完成时置为-1
    int rehashidx;
    int iterators;
} dict;
  • rehash期间,dict的操作如下

    • 增:仅添加在ht[1]中
    • 删:从ht[0]中找到了则删除ht[0],如果找不到则到ht[1]删
    • 查:先从ht[0]中查,查不到再从ht[1]中查
    • 改:先查(如上,会查找两个表),再修改节点
  • 为了防止有的KV长时间不访问一直不迁移到ht[1],会定时去rehash,每次迁移100个(如下)

int dictRehashMilliseconds(dict *d, int ms) {
    // 记录开始时间
    long long start = timeInMilliseconds();
    int rehashes = 0;
    // 每次迁移100个
    while(dictRehash(d,100)) {
        rehashes += 100;
        // 如果时间已过,跳出
        if (timeInMilliseconds()-start > ms) break;
    }
    return rehashes;
}
  • 字典操作不断执行后,最终ht[0]键值对都会被rehash到ht[1],将rehashidx置为-1并释放ht[0]

参考

美团针对Redis Rehash机制的探索和实践

书籍:redis设计与实现

你可能感兴趣的:(c语言,数据库,redis,数据库)