Redis学习笔记-Reis数据结构

在Redis中有五种基本的数据类型,分别是String(字符串)、List(列表)、Hash(哈希)、Set(集合)和有序集合(Sorted Sort)。下次我们详细了解一下,这些在Redis中是如何保存这些数据类型的。

Redis如何保存这些数据类型?

Redis是一个Key-Value的非关系型数据库。说到Key-Value,我们自然能想到哈希表。

Redis也是使用了一个哈希表来保存所有的键值对。

一个哈希表,其实就是一个数组,数组中的每个元素称为一个哈希桶,每个哈希桶中保存了键值对信息。

在Redis中,每个哈希桶保存的并不是值本身,而是指向具体值的指针。如下图所示:

在这里插入图片描述
因为这个哈希表保存了所有的键值对,所以可以称之为全局哈希表。哈希表最大的好处就是可以让我们用O(1)的时间复杂度老快速查找到键值对——我们只需要计算键的hash值,就可以知道它所对应的哈希桶的位置,然后就可以访问相应的entry元素。

哈希表存储有一个潜在的问题就是,当存储大量的数据的时候哈希表的冲突问题和rehash可能带来的操作阻塞

Redis如何解决Hash冲突

哈希冲突:就是两个Key计算哈希值后,落入到了同一个哈希桶中。

Redis解决哈希冲突的方式,就是使用链表。就是当多个元素需要保存在同一个哈希桶中时,用一个链表来保存这些元素。

如下图所示:entry1、entry2和entry3都需要保存在哈希桶3中,导致了hash冲突。此时,entry1元素就会通过一个*next指针指向entry2,同样,entry2也会通过*nxet指针指向entry3。这样,即使哈希桶中的元素再多,也可以通过entry中的指针连接起来。这就形成了一个链表,也叫做hash冲突链。
在这里插入图片描述

这就会导致另外一个问题,如果一个桶上的元素过多,这个链表过长,操作元素时只能通过指针逐一查找在操作。这就导致元素查询耗时长,效率降低。这时候就需要rehash。

rehash:就是增加hash桶的数量,让元素尽可能在哈希桶中分散保存,减少单个桶中的元素数量。

为了使rehash操作更加高效,Redis默认使用了两个全局哈希表:哈希表1和哈希表2。一开始,当你刚插入的数据的时候,默认使用哈希表1,此时的哈希表2并没有分配空间,随着数据逐步增多,Redis开始执行Rehash,这个过程分为三步:

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

此时,我们就可以从哈希表1切换到哈希表2,使用容量较大的哈希表2保存数据,哈希表1则留作下一次rehash使用(也可以认为这时候表1和表2的位置发生了互换)。

这其中第二步操作涉及到了大量的数据拷贝,如果一次性把hash表1中的数据都迁移完,会造成Redis线程阻塞,无法服务其他请求。

为了避免这个问题,redis采用了渐进式rehash。

简单的说就是在第二步拷贝数据是,Redis仍然正常处理客户端请求,每处理一个请求时,会将请求的元素对应的哈希桶上面的所有entries拷贝到哈希表2中,如下图所示:
在这里插入图片描述

通过这样的操作,将一次性大量拷贝的工作,分摊到了多次请求出过程中,避免了耗时操作,保证了数据的快速访问。

Redis底层数据结构

Redis的底层数据结构一共有6种,分别为简单动态字符串(SDS)、双向链表、压缩链表、哈希表、跳表和证书数组,它们和数据类型的对应关系如下图所示。
在这里插入图片描述

下面主要介绍一下压缩列表和跳表。

压缩列表实际上类似一个数组。和数组不同的是,压缩列表在表头有三个字段zlbytes、zltail和zllen,分别代表列表长度、列表尾部的偏移量和列表中的entry个数;压缩列表在表尾还有一个zlend,表示列表结束。
在这里插入图片描述

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

跳表你可以认为是一种特殊的链表。链表查找元素只能逐一查找,而跳表在链表的基础上增加了多级索引,从而可以快速定位元素。
在这里插入图片描述

如果我们要查找21这个元素,如果在链表中,只能从头开始遍历链表,直到找到21为止,一共需要查找6次。

现在我们在这个链表上增加一级索引:从第一个元素开始,每两个元素选一个作为索引。这些索引再通过指针指向原始的链表。此时我们只需要4次查找就能定位到元素21。

我们还可以在一级索引的基础上增加二级索引。这样,我们只需要三次查找,就能定位33了。

可以看到这个过程就是在多级索引上跳来跳去,最后定位到元素。所以叫做跳表。

不同的数据结构的查找时间复杂度如下:

名称时间复杂度
哈希表O(1)
跳表O(logN)
双向链表O(N)
压缩列表O(N)
整数数组O(N)

不同操作的复杂度

在Redis中,不同操作的复杂度时不一样的。

  • 单元素的操作的复杂度是由采用的数据结构决定的,例如HGET、HSET、和HDEL是对哈希表做操作,所以他们的复杂度都是O(1);Set类型用哈希表作为底层数据结构时,它的SADD、SREM、SRANDMEMBER复杂度也O(1)。但是当你对多个元素进行操作时,此时操作的复杂度,就是有单个元素复杂度和元素个数决定的。例如,HMSET增加M个元素,复杂度就从O(1)变成O(M)。

  • 范围操作,是指集合类型中的遍历操作,可以返回集合中的所有数据。这类操作的复杂度一般是O(N),比较耗时,我们应该尽量避免。不过,Redis从2.8版本提供了Scan系统操作(包括HSCAN,SSCAN和ZSCAN),这类操作实现了渐进式遍历,每次只返回有限数量的数据。避免了一次性返回所有的元素而导致的Redis阻塞。

  • 统计操作,是指集合类型对集合中所有元素个数的记录。当集合类型采用压缩列表,双向列表,整数数组这些数据结构时,这些结构中专门记录了元素的个数统计,因此可以高效的完成相关操作。

  • 例外情况,对于底层数据结构是压缩列表和双向链表的集合类型来说。比如,List的LPOP、RPOP、LPUSH、RPUSH这四个操作来说,它们只是在列表的头尾增删元素,这样可以通过偏移量直接定位,所以它们的复杂度也只有O(1),可以实现快速操作。

总结来说就是:

  • 单元操作最基础;

  • 范围操作非常耗时;

  • 统计操作通常高效;

  • 例外情况只有几个。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值