Redis中的渐进式Rehash机制

  哈希冲突链上的元素只能通过指针逐一查找再操作。如果哈希表里写入的数据越来越多,哈希冲突可能也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低。对于追求“快”的 Redis 来说,这是不太能接受的。

  所以,Redis 会对哈希表做 rehash 操作,也就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。

1. Rehash的需求与问题

在Redis中,数据量的增加是一个常见的情况,为了保证系统的性能和可用性,需要对存储数据的数据结构进行动态扩容。

而Rehash就是在这种情况下被引入的关键操作,Rehash操作的需求以及可能带来的问题:

1)动态扩容的必要性: 随着业务的发展和数据量的增加,Redis需要能够动态地扩容以应对不断增长的数据负载。否则,一旦达到内存容量的极限,系统将无法继续接收新的数据,严重影响系统的可用性和扩展性。

2)Rehash可能带来的性能问题: 传统的Rehash操作可能会导致性能下降,因为它需要将所有键重新计算哈希值,并重新分配到扩容后的哈希表中。在数据量巨大的情况下,这个过程可能会耗费大量的时间和计算资源,导致系统在扩容期间出现延迟甚至服务不可用的情况。

3)Redis如何处理Rehash的挑战: Redis通过一些优化策略来尽量减少Rehash可能带来的性能问题,例如渐进式Rehash。然而,即使有了优化,仍然需要仔细设计和调整,以确保在扩容过程中系统能够保持稳定性和高性能。

2.渐进式Rehash的原理

在传统的Rehash中,当哈希表需要扩容时,Redis会一次性地将所有键重新计算哈希值,并重新分配到新的哈希表中。这种方式可能会导致在数据量巨大时的性能问题。为了解决这个问题,Redis引入了渐进式Rehash技术,其原理如下:

渐进式Rehash的概念解释: 渐进式Rehash是一种渐进式地迁移数据的方式。它将Rehash操作分解成多个小步骤,每次只迁移一小部分数据,以避免在一次性操作中造成的性能抖动。

如何实现渐进式Rehash: 渐进式Rehash的实现方式通常是通过在扩容过程中同时维护两个哈希表:旧哈希表和新哈希表。在初始阶段,新的哈希表是空的,而所有的读写操作仍然在旧哈希表上进行。然后,Redis会逐步将旧哈希表中的数据迁移到新哈希表中。在迁移过程中,读操作可以在两个哈希表上同时进行,而写操作则只在旧哈希表上进行。当所有数据都成功迁移后,Redis会停止在旧哈希表上的写操作,并将所有操作都转移到新哈希表上,完成整个Rehash过程。

3. 渐进式Rehash的详细步骤

(1)为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。

(2)在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。

(3)在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。

(4)随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1,表示 rehash 操作已完成。

渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。

4. 渐进式Rehash的扩容与缩容

1)扩容

通过<<左移位运算保证是2的n次幂,扩容到大于dictEntry的数量used为止。
注意下面的size并不是哈希桶的数量,而是d->ht_used[0] + 1,也就是dictEntry的数量。

比如:哈希表(数组)长度为4,但是里面有9个哈希键值对(dictEntry),那应扩容到16(比9大的最小2的n次幂)。

复制代码
static signed char _dictNextExp(unsigned long size)
{
    unsigned char e = DICT_HT_INITIAL_EXP;

    if (size >= LONG_MAX) return (8*sizeof(long)-1);
    while(1) {
        if (((unsigned long)1<<e) >= size)
            return e;
        e++;
    }
}
复制代码

2)缩容(定时任务检查)

负载因子(used/size)小于0.1就会进行缩容,默认HASHTABLE_MIN_FILL=10,把100移过来就是0.1。

used表示有多少个 键值对实体(dictEntry),used越多,哈希冲突的情况越多。

size表示哈希表的大小,也就是哈希桶的个数。

比如哈希表size大小是32,那么实际used数量为3的时候,3/32<0.1那会进行缩容。

复制代码
int htNeedsResize(dict *dict) {
    long long size, used;

    size = dictSlots(dict);
    used = dictSize(dict);
    return (size > DICT_HT_INITIAL_SIZE &&
            (used*100/size < HASHTABLE_MIN_FILL));
}
复制代码

5. 渐进式Rehash的实际应用

渐进式Rehash技术在Redis中的实际应用具有重要意义,尤其在大规模数据处理和高并发访问的场景下,其优势更为明显。以下是一些渐进式Rehash的实际应用案例和其对Redis高可用性和性能保证的影响:

在Redis集群中的应用案例: 在Redis集群中,当新增节点或者集群规模扩大时,为了保证整个集群的负载均衡,需要对数据进行重新分片。传统的Rehash会导致集群在扩容期间的性能下降和服务中断,而渐进式Rehash技术可以使数据迁移过程更加平滑,减少对集群性能的影响,确保集群的高可用性。

渐进式Rehash与Redis的高可用性和性能保证: 在生产环境中,Redis需要保证高可用性和稳定性。通过采用渐进式Rehash技术,Redis能够在扩容过程中避免大规模的性能抖动,保持系统的稳定性和可用性。这对于对实时性要求较高的应用场景尤为重要,例如在线游戏、金融交易等。

6. 渐进式Rehash的优点和局限性

优点: 能够显著降低Rehash过程中的性能开销,使得系统能够在扩容过程中保持稳定性和高性能。(避免了redis阻塞

局限性:占用额外的内存空间用于同时维护两个哈希表,并且在迁移过程中可能会导致一定程度的数据不一致,另外,redis内存使用量瞬间突增,在Redis 满容状态下由于Rehash会导致大量Key驱逐。

总结

  渐进式Rehash技术在实际应用中发挥着重要作用,它能够有效地解决传统Rehash可能带来的性能问题,提升系统的稳定性和可用性。对于需要对Redis进行动态扩容和集群管理的场景来说,渐进式Rehash是一种重要的技术手段,它作为解决Redis动态扩容过程中性能问题的重要手段,可以帮助开发者构建高性能、可靠的Redis应用系统,具有以下几个方面的重要性和优势:

1)提升系统性能和稳定性: 渐进式Rehash技术能够有效地降低Rehash操作对系统性能的影响,使系统在扩容过程中能够保持稳定性和高性能,从而提升了系统的可用性和稳定性。

2)保证数据一致性: 渐进式Rehash将Rehash操作分解成多个小步骤,可以减少数据迁移过程中可能出现的数据不一致问题,保证了数据的一致性。

3)促进Redis集群的健康发展: 在Redis集群中,渐进式Rehash技术能够帮助实现节点的动态扩容和数据的重新分片,从而促进了Redis集群的健康发展和高可用性。

  总的来说,渐进式Rehash技术在Redis中具有重要的意义,它为Redis用户提供了一种有效解决动态扩容过程中性能问题的方案。未来,随着Redis的不断发展和技术的进步,可以期待更多关于渐进式Rehash技术的优化和改进,以满足不断增长的数据需求和业务场景的变化。

posted @   李若盛开  阅读(1431)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示