Redis为何这么快?

为啥就Redis这么突出?

  • 它是内存数据库,所有操作都在内存上完成,内存的访问速度本身就很快

  • 数据结构

键值对是按一定的数据结构来组织的,操作键值对最终就是对数据结构进行增删改查操作,所以高效的数据结构是Redis快速处理数据的基础

String(字符串)、List(列表)、Hash(哈希)、Set(集合)和Sorted Set(有序集合)只是Redis键值对中值的数据类型,即数据的保存形式。

本文的数据结构,是研究其底层实现。

底层数据结构一共6种:简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。

List、Hash、Set和Sorted Set这四种数据类型都有两种底层实现结构。通常称为集合类型:一个K对应一个集合的数据。

这些数据结构都是V的底层实现,K.V之间用什么组织的?

==========================================================================================

为什么集合类型有这么多底层结构,是怎么组织数据的,都很快吗?

什么是简单动态字符串,和常用的字符串是一回事吗?

Redis中有哪些潜在的“慢操作”,最大化Redis的性能优势。

键和值用什么结构组织?


为了实现从K到V快速访问,Redis使用哈希表保存所有KV对。

其实就是一个数组,数组元素称为哈希桶。一个哈希表由多个哈希桶组成,每个哈希桶中保存KV对。

如果值是集合类型,数组元素的哈希桶怎么保存呢的?

哈希桶中的元素保存的并非值本身,而是指向具体值的指针。即不管值是String,还是集合类型,哈希桶中的元素都是指向它们的指针。

哈希桶中的entry元素中保存了*key*value,分别指向实际的K、V。

即使值是个集合,也可通过*value指针查到。

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

全局指Redis数据库中的所有kv,是由一个哈希表来索引的。通过在这个哈希表中查询key,就可以找到对应v。然后根据v具体类型(如Hash,Set,List),再通过v的底层数据结构来读取具体的value数据,例如List通过双向链表来读取数据。

哈希表造就O(1)快速查找KV对,只需计算K的哈希值,即可知其对应哈希桶位置,然后就能访问相应entry元素。

这个查找过程主要依赖哈希计算,和数据量无直接关系。不管哈希表有10万个K还是100万,只需一次计算,就能找到对应K。

若你只是了解哈希表的O(1)复杂度和快速查找特性,那当你往Redis写入大量数据后,就可能发现操作有时候会突然变慢。

因为你忽略了潜在风险:哈希表冲突问题和rehash可能带来操作阻塞。

为什么哈希表操作变慢了?

===========================================================================

当你往哈希表中写入更多数据,就会哈希冲突。Redis通过链式哈希解决:同一哈希桶中的多个元素用链表保存。

entry1、entry2和entry3都要保存在哈希桶3:entry1会通过一个*next指向entry2,以此类推。这就形成了一个链表,也叫哈希冲突链。

哈希表数据越多,哈希冲突可能也越多,导致某些哈希冲突链过长,该链上的元素查找耗时长,效率降低。这对求“快”的Redis无法接受。

所以,Redis会对哈希表做

rehash

=====================================================================

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

为使rehash操作更高效,Redis默认使用了两个全局哈希表:刚插入数据时,默认使用哈希表1,哈希表2尚未被分配空间。

随着数据逐步增多,Redis开始执行rehash:

  1. 给哈希表2分配更大的空间,例如是当前哈希表1大小的两倍

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

  3. 释放哈希表1的空间

至此,即可从哈希表1切换到哈希表2:

  • 更大的哈希表2保存数据

  • 哈希表1留作下次rehash扩容备用

step2涉及大量数据拷贝,若一次性把哈希表1中的数据都迁移完,会造成Redis线程阻塞,无法服务其他请求,Redis就无法快速访问数据了。为解决该问题,Redis采用渐进式rehash

step2拷贝数据时,Redis仍可正常处理客户端请求:

  • 每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带将该索引位置的所有节点复制到哈希表2

  • 等到处理下一个请求时,再复制哈希表1中的下一个索引位置的节点们

这就把一次性大量拷贝开销,分摊到多次处理请求过程:

  • 避免了耗时操作

  • 保证了数据访问的快

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

但对集合类型,即使找到哈希桶了,还要在集合中进步操作。

dict的渐进式rehash是为了避免扩容时的整体拷贝,这会给内存带来较大压力。

集合数据操作效率

=======================================================================

一个集合类型的值:

  1. 通过全局哈希表找到对应的哈希桶位置

  2. 在集合中再增删改查

影响因素


底层数据结构

使用哈希表实现的集合,要比使用链表实现的集合访问效率更高。

操作效率和这些操作本身的执行特点有关,比如读写一个元素的操作要比读写所有元素的效率高。

整数数组和双向链表都是顺序读写,时间复杂度基本是O(N),效率较低。

压缩列表

类似数组,数组中每个元素对应一个数据。

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

  • 第一个元素、最后一个元素,可通过表头三字段的长度直接定位,复杂度O(1)

  • 查找其他元素时,就只能逐个查找,复杂度O(N)

跳表

有序链表只能逐一查找元素,非常慢,于是出现跳表。

跳表基于链表,增加多级索引,通过索引位置跳转,快速定位数据:

查找过程就是在多级索引上跳来跳去,最后定位到元素。故名“跳”表。

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

不同操作的复杂度

不同操作的复杂度

集合类型的操作类型:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值