Redis数据结构|青训营笔记

94 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记

Redis数据结构底层实现

底层数据结构一共有 6 种,分别是

  • 简单动态字符串 O(1)
  • 双向链表 O(n)
  • 压缩列表 O(n)
  • 哈希表 O(1)
  • 跳表 O(logN) 本文主要讲Redis全局哈希表

Redis全局哈希表

Redis对所有的键值对,有一个全局的hash表来存储。一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。每个哈希桶中保存了键值对数据。

哈希表的最大好处就是可以以 O(1) 的时间复杂度快速查找到键值对:
只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。这个查找过程主要依赖于哈希计算,和数据量的多少并没有直接关系。

但是,当往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。这是由于大量数据导致的哈希表的冲突问题和rehash 可能带来的操作阻塞。

Redis解决hash冲突的方法就是rehash和链式存储:

  • 链式存储: 链式哈希,就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。Redis解决hash冲突的方式是链地址法,但是链表搜索元素的时间复杂度是O(n)。
  • rehash rehash 也就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突,缓解了链表查询慢的问题。
    具体操作: Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步:
  1. 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;

  2. 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;

  3. 释放哈希表 1 的空间。

到此,我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来的哈希表 1 留作下一次 rehash 扩容备用。

这个过程和hashmap的扩容原理类似,但是当存在数百万级的数据rehash操作时,redis便会停止服务,cpu全用来rehash了,这样情况是我们不愿意看到的,为了避免这个问题,Redis采用了渐进式 rehash。

  • 渐进式rehash

简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求。

请求类型(删除、查找、更新):在进行渐进式rehash的过程中,字典会同时使用 1 和 2 两个哈希表,所以在渐进式rehash进行期间,字典的删除、查找、更新等操作会在两个哈希表上进行。比如说,要在字典里面查找一个键的话,程序会先在哈希表 1 里面进行查找,如果没找到的话,就会继续到哈希表 2 里面进行查找。

请求类型(新增):在渐进式 rehash 执行期间,新添加到字典的键值对一律会被保存到哈希表 2 里面,而哈希表 1 则不再进行任何添加操作。这一措施保证了哈希表 1 包含的键值对数量会只减不增,并随着rehash操作的执行而最终变成空表。