ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

Redis核心技术与实战-学习笔记(二)

2022-01-20 20:31:52  阅读:319  来源: 互联网

标签:核心技术 复杂度 元素 Redis 笔记 查找 哈希 操作


一.redis数据类型和底层数据结构的对应关系

     

二.Redis 使用哈希表实现从键到值的快速访问

哈希表的O(1)复杂度和快速查找特性

一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。

一个哈希表是由多个哈希桶组成的,每个哈希桶中保存了键值对数据。

不管值是 String,还是集合类型,哈希桶中的元素都是指向它们的指针地址。

哈希桶中的entry元素保存了*key和*value指针,分别指向了实际的键和值,这样一来。即使值一个集合,也可以通过*value指针被查找到。

哈希表最大的好处就是可以用O(1)的时间复杂度来快速查找键值对

---我们只需计算键的哈希值,就可以知道哈希桶的位置,然后访问相应的entry元素。

三.哈希表的冲突问题和rehash可能带来的操作阻塞。

    当你往哈希表中写入更多数据时,哈希冲突是不可避免的问题。这就是哈希冲突,两个key的哈希值和哈希桶计算对应关系时,正好落到同一个哈希桶中。

      Redis 解决哈希冲突的方式,就是链式哈希。链式哈希也很容易理解,就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。

     

  但是,这里依然存在一个问题,哈希冲突链上的元素只能通过指针逐以查找再操作。如果哈希表里写入的数据越来越多,哈希冲突越来越多,会导致哈希冲突链过长,进而导致这个链上的元素查找耗时过长,效率降低。

      Redis会对哈希表做rehash操作。rehash也就是增加现有的哈希桶数量,让逐渐增多的entry元素能在更多桶之间分散保存,减少单个桶中的元素数量,从而减少哈希冲突。

     为了使rehash操作更高效,Redis默认使用两个全局哈希表:哈希表1和哈希表2。

      一开始当插入数据时,默认使用哈希表1,此时哈希表2并没有被分配空间。随着数据的增多redis开始rehash,这个过程分为三步:

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

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

      3.释放哈希表1的空间

     因此我们将哈希表1切换到哈希表2,用增大的哈希表2保存更多的数据,而原来的哈希表1留作下一次rehash扩容备用。这个过程第二步涉及大量数据拷贝,如果一次性把哈希表1中的数据都迁移完,会造成Redis线程阻塞没无法服务其他请求。此时,Redis无法处理其他请求。

四.渐进式 rehash

      1.进行第二步拷贝数据时,Redis仍然可以正常处理客户端请求,

        每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带着将这个索引位置上的所有entries拷贝到哈希表2中;即将哈希冲突链复制过去。下一次也一样

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

      渐进式rehash执行时,除了根据键值对的操作来进行数据迁移,redis本身还有一个定时任务来执行,会周期性的搬移数据到新哈希表中,缩短整个rehash的过程。

五.集合数据操作效率

      Redis 的键和值是怎么通过哈希表组织的了。对于 String 类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的 O(1) 操作复杂度也就是它的复杂度了。

     一个集合类型的值,第一步是通过全局哈希表找到对应的哈希桶位置,第二步在集合中再进行增删改查。

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

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

查找第一个元素,时间复杂度是O(1);

查找最后一个元素通过zltail时间复杂度是O(1);

查找其他元素时候,只能逐个查找,时间复杂度是O(n);

跳表

    有序链表只能逐一查找元素,导致操作起来非常缓慢,于是就出现了跳表。

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

    (链表的二分查找)

跳表的核心是链表是有序的,所以查找时间复杂度跟二分查找一样是O(logn)

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

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

不同操作的复杂度

  1. 单元素操作是基础;
  2. 范围操作非常耗时;
  3. 统计操作通常高效;
  4. 例外情况只有几个; 

第一.单元素操作是指每一种集合类型对单个数据实现的增删改查操作。

hash类型的hget,hset,hdel,是对哈希表或压缩列表操作,时间复杂度是O(1);

set类型sadd,srem,srandmember是对哈希表或者整数数组操作,时间复杂度是O(1);

hash 类型的 HMGET 和 HMSET,Set 类型的 SADD 也支持同时增加多个元素复杂度就从 O(1) 变成 O(M) 了。

第二,范围操作,是指集合类型中的遍历操作,可以返回集合中的所有数据。

hash类型中的hgetall

set类型中的smembers

或者返回一个范围内的部分数据,

list类型的lrange和zset类型的zrange。

时间复杂度是O(n)。比较耗时,我们应该尽量避免。

Redis从2.8版本开始提供SCAN系列操作(包括HSCAN,SSCAN和ZSCAN),这类操作实现渐进式遍历,每次只返回有限数量的数据。避免相比如hgetall,smembers这种操作一次性返回所有元素导致的redis阻塞。

第三,统计操作,是指集合类型对集合中所有元素个数的记录

      例如LLEN和SCARD。这类操作时间复杂度只有O(1),这些集合采用压缩列表,双向链表或者整数数组的时候,会专门记录元素的个数统计,以空间换时间的形式减少时间复杂度。

第四,例外情况

         压缩列表和双向链表会记录表头和表尾的偏移量,这样对于list类型的lpop,rpop,lpush,rpush四个操作来说,可以直接通过偏移量定位尾部元素,时间复杂度也是O(1),实现快速操作。

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

       1.内存利用率,数组和压缩列表都是非常紧凑的数据结构,他比链表占用内存少。redis是内存数据库,大量数据存在内存中,此时需要做尽可能的优化,提高内存利用率。

       2.数组对CPU告诉缓存支持更友好,所以redis在集合数据元素较少的情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阀值,避免查询时间过高,转为哈希表和跳表数据结构存储,保证查询效率。

 

     

  

     

   

    

     

      

标签:核心技术,复杂度,元素,Redis,笔记,查找,哈希,操作
来源: https://blog.csdn.net/weixin_42369687/article/details/122579263

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有