Redis核心技术笔记——Redis数据结构

Redis底层数据结构

​ 总体来说,大家都知道redis数据结构有String、List、Hash、Set、Sorted Set还有三种高级的数据结构Bit map、GEO、Hyperloglog。

​ 简单来说,redis底层数据结构一共只有6种,分别是简单动态字符串(SDS)、双向链表、压缩列表、哈希表、跳表和数组,首先我会分析他们对于redis中数据结构的实现,然后我会给出三种高级数据结构的应用场景还有redis索引的实现

1.Redis中数据类型和底层数据结构的关系

如图所示:
请添加图片描述
​ 可见,string采用了sds,List采用了双向链表和压缩列表,hash采用了压缩列表和哈希表。set采用了哈希表和整数数组。sorted set采用了压缩列表和跳表

为什么要如此设计,为什么集合类型的底层都有两种数据结构,什么时候用哪一种数据结构呢,等等问题接下来我会尽最大能力解答

2.redis索引的实现——redis是如何根据操作快速访问到对应数据的

​ 一句话总结:Redis采用了一个哈希表来保存所有的键值对映射关系

​ 一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。所以,我们常说,一个哈希表是由多个哈希桶组成的,每个哈希桶中保存了键值对数据。

看到这里,你可能会问了:“如果值是集合类型的话,作为数组元素的哈希桶怎么来保存呢?”其实,哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。这也就是说,不管值是String,还是集合类型,哈希桶中的元素都是指向它们的指针。

在下图中,可以看到,哈希桶中的entry元素中保存了*key*value指针,分别指向了实际的键和值,这样一来,即使值是一个集合,也可以通过*value指针被查找到。
请添加图片描述
​ 如图就像这样的数据结构关系,就可以保证redis可以快速的通过key找到对应的value所在的地址,理想的情况下时间复杂度就为o(1),当存在哈希冲突可能就会变慢

2.1 解决索引哈希表的哈希冲突

​ 当你往哈希表中写入更多数据时,哈希冲突是不可避免的问题。如上图,两个entry在同一个位置,这就是哈希冲突,哈希冲突会把时间复杂度为o(1)的查询变成o(n)

​ Redis解决哈希冲突的方式,就是链地址冲突法——同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接

​ 如下图所示:entry1、entry2和entry3都需要保存在哈希桶3中,导致了哈希冲突。此时,entry1元素会通过一个*next指针指向entry2,同样,entry2也会通过*next指针指向entry3。这样一来,即使哈希桶3中的元素有100个,我们也可以通过entry元素中的指针,把它们连起来。这就形成了一个链表,也叫作哈希冲突链。
请添加图片描述
​ 链地址冲突法解决哈希冲突存在一个问题:哈希冲突越来越多,就会导致哈希冲突链越来越长,降低了效率

​ 解决方式:,Redis会对哈希表做rehash操作。rehash也就是增加现有的哈希桶数量,让逐渐增多的entry元素能在更多的桶之间分散保存,减少单个桶中的元素数量,为了使rehash操作更高效,Redis默认使用了两个全局哈希表:哈希表1和哈希表2。一开始,当你刚插入数据时,默认使用哈希表1,此时的哈希表2并没有被分配空间。随着数据逐步增多,Redis开始执行rehash,这个过程分为三步:

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

在进行rehash扩容降低哈希冲突链的时候,在进行第二步操作时有一个问题:如何才能在大量数据拷贝时不阻塞redis线程

​ 为了避免这个问题,redis采用了——渐进式rehash算法;即一边操作一边进行拷贝,Redis仍然正常处理客户端请求,每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带着将这个索引位置上的所有entries拷贝到哈希表2中;等处理下一个请求时,再顺带拷贝哈希表1中的下一个索引位置的entries。如下图所示:
请添加图片描述
​ **对于String类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的O(1)操作复杂度也就是它的复杂度了。对于集合类型来说,即使找到哈希桶了,还要在集合中再进一步操作。**接下来,我们来看集合类型的操作效率又是怎样的。

3.压缩列表和跳表

​ 哈希表的操作特点我们刚刚已经学过了;整数数组和双向链表也很常见,它们的操作特征都是顺序读写,也就是通过数组下标或者链表的指针逐个元素访问,操作复杂度基本是O(N)

压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同的是,压缩列表在表头有三个字段zlbytes、zltail和zllen,分别表示列表长度、列表尾的偏移量和列表中的entry个数压缩列表在表尾还有一个zlend,表示列表结束
请添加图片描述
​ 压缩列表中entry是由 prev_len、len、encoding、content组成

  • prev_len,表示前一个entry的长度。prev_len有两种取值情况:1字节或5字节。取值1字节时,表示上一个entry的长度小于254字节。虽然1字节的值能表示的数值范围是0到255,但是压缩列表中zlend的取值默认是255,因此,就默认用255表示整个压缩列表的结束,其他表示长度的地方就不能再用255这个值了。所以,当上一个entry长度小于254字节时,prev_len取值为1字节,否则,就取值为5字节。
  • len:表示自身长度,4字节
  • encoding:表示编码方式,
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值