当前位置:   article > 正文

Redis:键值存储结构(压缩列表、跳表等)_redis存键值方式

redis存键值方式

【关于作者】

关于作者,目前在蚂蚁金服搬砖任职,在支付宝营销投放领域工作了多年,目前在专注于内存数据库相关的应用学习,如果你有任何技术交流或大厂内推及面试咨询,都可以从我的个人博客(https://0522-isniceday.top/)联系我

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhyqWPIV-1680881339714)(https://zhangyuxiangplus.oss-cn-hangzhou.aliyuncs.com/boke//Redis%E9%94%AE%E5%80%BC%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84_1618237855039.png)]

1.Redis value的数据类型

String、List、Hash、Set、Zset

2.Redis的value的底层数据结构

底层数据结构一共有六种:

(1)简单动态字符串

(2)双向链表

(3)压缩列表

(4)哈希表

(5)跳表

(6)整数组

其中数据类型和数据结构的关系如下:

img

这里我们将数据分为两种类型,字符串类型和集合类型,集合类型我们发现都有两种底层实现结构

3.redis的键和值用的组织结构

我们知道了value底层的数据结构之后,那么键和值又是如何组织的呢?

redis使用了哈希表来保存键值对,哈希表使用的是数组,数组中的每个元素存储的称之为哈希桶,而哈希桶(entry)保存了键值对的实际数据

,其实一个哈希桶当中也只是保存了键和值的指针,即便值是一个集合,也能通过*value找到,这个保存了所有键值对的哈希表我们称为全局哈希表,其数据结构如下图:

img

虽然说哈希表再最优情况下能够做到O(1),但是当数据变多的时候还是会发生哈希冲突,这个时间我们就需要再哈希(reHash),而redis全局哈希表解决哈希冲突采取的是拉链法,如下图:

img

但是当哈希值底下的链路过长,也难免会造成查询时间边长,那这个时候该如何避免单个哈希桶中的元素数量尽量减少,哈希桶之间的冲突尽量减少呢

redis为了使hash更加高效,redis默认使用了两个全局哈希表,哈希表1和哈希表2,刚插入数据时候默认使用哈希表1,此时哈希表2并未分配空间,随着数据增大,这个时候会将哈希表1中的数据再哈希到数据表2,主要分为如下三步

(1)给哈希表2分配更大的空间,例如哈希表1的两倍

(2)将哈希表1的数据全部拷贝到哈希表2

(3)释放哈希表1的空间

但是这个过程中如果数据量很大,那么拷贝的过程中势必会很耗时,可能会堵塞正常请求,这个时候redis就采取了渐进式哈希

就是当第二步拷贝数据时,redis正常处理用户请求,每当用户请求到哈希桶1的数据,就将哈希桶1的数据上的所有entry拷贝到哈希表2,

如下图:

img

可以看到经过渐进式hash,哈希表2的数据更加稀疏

接下来再看找到了value之后,集合类型的操作效率如何

4.集合数据操作效率

前面我们已经知道了集合结构包含了五种数据结构,其中哈希表再上面已经讲过了,整数数组和双向链表比较常见,通过数组下标或者链表的遍历来定位元素,操作的复杂度基本都为O(n)

压缩列表:

压缩列表实际上类似于一个数组,只是表头有三个特殊的元素:zlbytes、zltail和zllen,分表表示列表长度、表尾偏移量、列表中entry的个数,列表结尾还有一个zlend,表示列表结束

查找第一个元素和队尾可以通过表头的三个元素直接查找到,为O(1),但是其他元素就只能遍历查找了,复杂度为O(N)

img

跳表:

有序链表查找数据很缓慢,这个时候可以通过建立多级索引,通过索引的多级跳转实现数据的快速定位。

如下图:

img

建立的索引层级越多,查找速度越快,这样通过索引的几次跳转就能够很快的定位到元素位置(类似于二分查找)。时间复杂度为O(logn)

所以value的底层数据结构时间复杂度比较如下:

img

5.不同操作的复杂度

针对不同的value数据类型,有不同的操作方式,例如Hget、Hset、SAdd,也有操作多个元素的,或者对整个元素进行遍历的,不同的操作时间复杂度又有不同,所以复杂度的高低又是我们选择集合类型的一个指标

四字口诀祝你规避搞复杂度的操作

(1)单元素操作是基础

(2)范围操作非常耗时

(3)统计操作通常高效

(4)例外情况只有几个

单元素操作是基础:

单元素操作是指每种集合元素的单元素的增删改查时间复杂度与所使用的数据结构一致

而集合支持对多个元素进行批量操作,这个时候时间复杂度就与单个元素的时间复杂度与元素个数有关,例如HMSET了M个元素,时间复杂度就是O(1xM)=O(M)

范围操作非常耗时:

范围操作是指集合的遍历操作,返回范围内的数据,例如HGETALL和Set类型的SMEMBERS,或者返回一个范围的数据,List类型的LRANGE和ZSet类型的ZRANGE。这类操作的复杂度一般是O(N),比较耗时,我们应该尽量避免

Redis 2.8x开始支持Scan操作,实现了渐进式遍历,避免了一次性返回所有元素导致的堵塞

统计操作通常高效:

这里是指集合类型对集合数据中所以元素个数的记录,集合类型采取数组、双向链表、压缩数组时这些结构专门记录了元素的个数

例外情况:

是指某些元素的特殊记录,例如压缩列表和双向链表都会记录表头和表头的偏移量,所以对了List结构的LPOP、RPOP、LPUSH、RPUSH这四个操作来说,它们是在列表的头尾增删元素,这就可以通过偏移量直接定位,所以它们的复杂度也只有O(1)

6.结论:

(1)对于List结构采取的压缩数据或双向链表,由于增删(Pop/Push)的复杂度为O(1),因此可以用于FIFO的队列场景,而不是作为随机读写(压缩表像不像使用数据实现了队列?)

(2)Redis的List底层使用压缩列表本质上是将所有元素紧挨着存储,所以分配的是一块连续的内存空间,虽然数据结构本身没有时间复杂度的优势,但是这样节省空间而且也能避免一些内存碎片

(3)用SCAN代替全量查找

f204bdcf37f31c7abcee065daed8dd2d

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/538570
推荐阅读
相关标签
  

闽ICP备14008679号