02 数据结构

02 数据结构

Redis接收到一个键值对操作后,能以微秒级别的速度找到数据,并快速完成操作。

因为一方面,这是因为它是内存数据库,所有操作都在内存上完成,内存的访问速度本身就很快;另一方面,这要归功于它的数据结构。

redis的数据结构

String 类型的底层实现只有一种数据结构,也就是简单动态字符串。而List、Hash、Set 和 Sorted Set 这四种数据类型,都有两种底层实现结构,我们会把这四种类型称为集合类型

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

一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶,每个哈希桶中保存了键值对数据,哈希桶中的元素保存的并不是值本身,而是指向具体值的指针

哈希表 O(1) 的时间复杂度来快速查找到键值对,只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。哈希桶中的 entry 元素中保存了*key*value指针,分别指向了实际的键和值。

因为这个哈希表保存了所有的键值对,把它称为全局哈希表


哈希冲突

两个 key 的哈希值和哈希桶计算对应关系时,两个 key 的哈希值正好落在了同一个哈希桶中,因为哈希桶的个数通常要少于 key 的数量。

解决方法:链式哈希。同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。

若某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,故Redis 会对哈希表做rehash 操作。

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

渐进式 rehash

Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中,如此等待下一个请求做同样操作。

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

对于 String 类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的 O(1) 操作复杂度也就是它的复杂度

对于集合类型来说,即使找到哈希桶了,还要在集合中再进一步操作


集合数据操作效率

集合类型的底层数据结构主要有 5 种:整数数组、双向链表、哈希表、压缩列表和跳表。

整数数组和双向链表:

操作特征都是顺序读写,通过数组下标或者链表的指针逐个元素访问,操作复杂度基本是 O(N),操作效率比较低。

压缩列表:

压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。

压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。

在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。

而查找其他元素时,只能逐个查找,此时的复杂度就是 O(N) 了。

跳表:

跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。

当数据量很大时,跳表的查找复杂度就是 O(logN)。


不同操作的复杂度


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

答:整数数组和压缩列表的设计,充分体现了 Redis“又快又省”特点中的“省”,也就是节省内存空间。整数数组和压缩列表都是在内存中分配一块地址连续的空间Redis 之所以采用不同的数据结构,其实是在性能和内存使用效率之间进行的平衡

posted @ 2024-02-19 18:42  zhyan0502  阅读(6)  评论(0编辑  收藏  举报