redis_redis是如何存储数据的
2025-01-22 08:19:30    1k 字   
This post is also available in English and alternative languages.

为了实现从key到value的快速访问,Redis使用哈希表来保存所有键值对。

本篇是学习极客时间《Redis核心技术与实战#数据结构:快速的Redis有哪些慢操作?》的笔记


1. 前言

如果了解HashMap,那本篇会很容易理解


2. 键值的组织结构

为了实现从key到value的快速访问,Redis使用哈希表来保存所有键值对,这个哈希表其实就是一个数组;数组的每个元素称为一个哈希桶;所以常说,一个哈希表是由多个哈希桶组成的,每个哈希桶中保存了键值对数据。

哈希桶中的entry元素中保存了key的指针和value的指针,分别指向了实际的键和值,这样一来,即使值是一个集合,也可以通过value指针被查找到。

全局哈希表(来源:极客时间-Redis核心技术与实战)

如上图所示,这个哈希表保存了所有的键值对,可以将它称为全局哈希表。

使用哈希表的最大好处很明显,就是可以用O(1) 的时间复杂度来快速查找到键值对,而使用者只需要计算key的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的entry元素。

这个查找过程主要依赖于哈希计算,和数据量的多少并没有直接关系;也就是说不管哈希表里有10 万个key还是100 万个key,只需要一次计算就能找到相应的键。

不过当往Redis中写入大量数据后,可能会发现操作有时候会突然变慢了,这其实可能是哈希表的冲突问题rehash带来的操作阻塞。


3. 为什么变慢了

向哈希表中写入更多数据时,哈希冲突是不可避免的问题。

这里的哈希冲突是指:两个key的哈希值相同,都落在了同一个哈希桶中;
毕竟哈希桶的个数通常要少于key的数量,这也就是说,难免会有一些key的哈希值对应到了同一个哈希桶中。

redis解决哈希冲突的方式是采用链式哈希(链表),就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。

这样就会出现一个问题,如果哈希表里写入的数据越来越多,哈希冲突可能也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,查找效率降低。

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

为了使rehash操作更高效,Redis默认使用了两个全局哈希表:哈希表1和哈希表2,
一开始,当刚插入数据时,默认使用哈希表1,此时的哈希表2并没有被分配空间;随着数据逐步增多,Redis开始执行rehash

渐进式rehash(来源:极客时间-Redis核心技术与实战)
  1. 给哈希表2分配更大的空间,例如是当前哈希表 1 大小的两倍;

  2. 渐进式rehash
    Redis仍然正常处理客户端请求,每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带着将这个索引位置上的所有entries拷贝到哈希表 2 中;
    等处理下一个请求时,再顺带拷贝哈希表1中的下一个索引位置的entries。

  3. 释放哈希表1的空间

这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。


4. Reference

  • 极客时间 -《Redis核心技术与实战#数据结构:快速的Redis有哪些慢操作?》