Redis底层数据结构和reHash过程(二)

本文详细介绍了Redis如何使用哈希表存储键值对,并探讨了哈希冲突的解决策略——链式哈希。文章重点讨论了reHash的过程,包括渐进式reHash的机制,以避免一次性数据迁移导致的服务阻塞。此外,还概述了Redis中压缩列表和跳表两种数据结构及其时间复杂度分析,解释了为何Redis选择这些数据结构的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis底层数据结构

​ Redis常用的类型有STRING(字符串)、LIST(列表)、SET(集合)、HASH(散列)、ZSET(有序集合)

img

Redis 键值对存储的结构

​ 为了实现从键到值的快速访问,Redis使用了哈希表来保存所有键值对。对应Redis设置的Key,而对应的并不是值本身,而是指向具体值的指针。使用哈希表的最大好处就是可以用O(1)的时间复杂度快速找到键值对。但既然是哈希表,那么必然会有着哈希冲突的问题。

​ 哈希冲突即指的是,当两个key的哈希值和哈希桶计算对应关系时,正好落在了同一个哈希桶上。

Redis解决哈希冲突的方式是使用链式哈希,即拉链法。当多个元素指向同一个哈希桶时,在同一个哈希桶中采用链表来保存对应的数据,它们之间依次用指针连接。

img

​ 链式哈希有个很明显的问题,就是当同一个桶的元素过多时,链表的长度会越来越长,在链表查询数据的时间复杂度又是O(n),意味着链越长查询时间就越久。针对这个问题,Redis会对哈希表进行reHash操作。

reHash的原理和过程

reHash操作即是增加现有的哈希桶数量,让逐渐增加的元素能在更多的桶位置之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。

​ 具体做法是,Redis默认使用了两个全局哈希表:哈希表1和哈希表2。一开始只有哈希表1插入数据,哈希表2并没有被分配空间。随着数据逐渐增多,Redis执行reHash操作,这个过程共分为三步:

  1. 给哈希表2分配更大的空间,例如是当前哈希表1的两倍;
  2. 把哈希表1中的数据重新映射并拷贝到哈希表2中;
  3. 释放哈希表1的空间;

迁移数据后,哈希表1做备份用,哈希表2做插入保存用;

Ps:有没觉得跟Java的JVM垃圾回收中的复制算法很像。

​ 在这个过程中,第二步操作涉及了大量的数据拷贝,如果一次性将哈希表1的数据都迁移完,那必然会造成Redis线程堵塞,无法服务其他请求。为了避免这个问题,Redis采用了渐进式reHash

渐进式reHash

​ 简单来说就是在第二步操作时,Redis仍然正常处理客户端请求,每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带着将这个索引位置上所有的entries元素拷贝到哈希表2中;等下一次请求时,再顺带拷贝下一个索引位置的entries。

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

疑问:但这样在处理请求中,迁移数据会不会影响到操作的效率问题?

数据类型的底层数据结构

1. 压缩列表(List、Hash、Sorted Set)

​ 类似于一个数组,数组中每一个元素都对应保存一个数据。和普通数组不同的是,压缩列表在表头有zlbytes、zltail 和 zlen,分别表示列表长度、列表尾的偏移量和列表中的entry个数;在表尾还有个zlend,表示列表结束。

img

如果是查找第一个元素或者最后一个元素,那么时间复杂度就是O(1),其它位置就是O(n)了。

2. 跳表(Sorted Set)

​ 有序列表只能逐一查找元素,导致操作起来非常缓慢,于是就有了跳表。跳表是在链表的基础上,新增了多级索引的概念,通过索引位置的几个跳转,实现数据的快速定位。

各种数据结构的时间复杂度

相关问题

Q:整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么Redis还会把它们作为底层数据结构呢?

A:

有两方面的原因。

一方面是内存利用率,数组和压缩列表都是非常紧凑的数据结构,它把链表占用内存要更少。而Redis是内存数据库,大量数据存在内存中,此时需要做尽可能的优化,提高内存利用率。

还有一方面是数组对CPU高速缓存支持更友好,所以Redis在设计时,集合元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。

Q: rehash过程中,读的流程是怎样,有什么方式减少两次读?
A:会先去旧表中读,如果旧表中无数据会去新表中读。但是这样会有可能导致两次读,延迟了读的时间。有什么办法可以减少两次读呢?在这里有个想法是,在rehash过程中记录当前已经hash的位置,这样当查询新数据时,比较新数据的hash位和rehash的位置,当hash位大于rehash位置时,说明还没有被rehash过去,即去旧表中读取即可。

参考资料

极客时间 – 《Redis核心技术与实战》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值