redis原理(二)数据结构

目录

一、STRING字符串:

1、介绍:

2、底层数据结构:

3、大小:

4、常用基本命令:

5、应用场景:

二、LIST列表:

1、介绍:

2、底层数据结构:

3、常用基本命令:

4、应用场景:

三、SET集合:

1、介绍:

2、底层数据结构:

3、常用基本命令:

4、使用场景:

四、HASH散列(字典):

1、介绍:

2、底层数据结构:

3、常用基本命令:

4、常用场景:

五、SortedSet (ZSET)有序集合:

1、介绍:

2、特点:

3、底层数据结构 :

4、常用基本命令:

5、应用场景: 


redis可以存储键与5种不同数据结构类型之间的映射:

String类型的底层实现只有一种数据结构,也就是动态字符串。而List、Hash、Set、ZSet都由两种底层数据结构实现。通常我们把这四种类型称为集合类型,它们的特点是一个键对应了一个集合的数据。下面分别介绍下

一、STRING字符串:

1、介绍:

redis中字符串可以存储普通字符串、整数、浮点数这三种类型,不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。

注:字符串类型的最大空间不能超过512M。

2、底层数据结构:

简单动态字符串(free、len、buf[])(可以保存文本+二进制+不会缓冲溢出+获取字符串长度时间[O1])。redis没有直接使用C语言传统的字符串表示,而是自己实现的叫做简单动态字符串SDS的抽象类型。C语言的字符串不记录自身的长度信息,而SDS则保存了长度信息,内部结构实现上类似于 Java 的ArrayList,这样将获取字符串长度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串长度时所需的内存重分配次数;

  • 对C语言中的字符串的封装和优化,c语言字符串不是二进制安全的,字符串中间不能有空格,空格标志结束
  • 频繁修改一个字符串时,会涉及到内存的重分配,比较消耗性能。(Redis中的简单动态字符串会有内存预分配和惰性空间释放)。
  • 如果字符串实际使用长度len<1M,实际分配空间=len长度来存储字符串+1字节存末尾空字符+len长度的预分配空闲内。
  • 如果字符串实际使用长度len>1M,实际分配空间=len长度来存储字符串+1字节存末尾空字符+1M长度的预分配空闲内存
3、大小:

当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M;

4、常用基本命令:

SET、GET、DEL、INCR、DECR

   

5、应用场景:

计数;

二、LIST列表:

1、介绍:

一个链表,链接上的每个节点都包含了一个字符串。一个列表结构可以有序地存储多个字符串,相同元素可以重复出现。特征也与LinkedList类似:

①有序 ②元素可以重复 ③插入和删除快 ④查询速度一般

2、底层数据结构:
2.1、早期版本

使用 linkedlist双端列表和 ziplist压缩列表。当列表对象满足以下两个条件的时候,List 将使用 ziplist 存储,否则使用 linkedlist。

  • List 的每个元素的占用的字节小于 64 字节。
  • List 的元素数量小于 512 个。
(1)ziplist:

当一个列表只有少量数据的时候,并且每个列表项要么是小整数值,要么就是长度比较短的字符串,那么我就会使用 ziplist 来做 List 的底层实现。ziplist 中可以包含多个 entry 节点,每个节点可以存放整数或者字符串:与 linkedlist 相比,少了 prev、next 指针,它将所有的元素紧挨着一起存储,分配的是一块连续的内存。

ziplist结构:

  • zlbytes:整个ziplist占字节数
  • zltail:尾结点相对于首地址偏移量
  • zllen:结点数
  • entry:保存了前一个结点长度+编码+内容
  • zlend:代表结束
(2)linkedlist:

数据多时用linkedlist(ziplist连锁更新耗时),当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。linkedlist维护前后指针,占内存空间,还造成内存碎片化

 2.2、Redis 3.2

引入了由 linkedlist + ziplist 组成的 quicklist。quicklist 是综合考虑了时间效率与空间效率引入的新型数据结构。结合了原先 linkedlist 与 ziplist 各自的优势,本质还是一个链表,只不过链表的每个节点是一个 ziplist。

2.3、7.0版本

使用 listpack 取代 ziplist。出现 listpack 的原因是因为用户上报了一个 Redis 崩溃的问题,但是 antirez 并没有找到崩溃的明确原因,猜测可能是 ziplist 结构导致的连锁更新导致的,于是就想设计一种简单、高效的数据结构来替换 ziplist 这个数据结构。

扩展:也可将list模拟队列和栈的使用。

3、常用基本命令:

LPUSH、RPUSH、LPOP、RPOP、LINDEX、LRANGE 

4、应用场景:

常用来存储一个有序数据

(1)发布与订阅或者说消息队列: Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理;存储任务信息

(2)阻塞式命令常用于消息传递、任务队列。

(3)慢查询:

(4)存储最近浏览过的数据,文章、商品;朋友圈点赞列表,评论列表等。

三、SET集合:

1、介绍:

和列表的区别在于Set是无序、去重的。

2、底层数据结构:

哈希表hashtable+整数数组intset。

set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:

  • 结合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过512个

Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。由于是set,有天然去重功能。Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap;因为也是一个hash表,因此具备与HashSet类似的特征:

①无序; ②元素不可重复; ③查找快; ④支持交集、并集、差集等功能;

3、常用基本命令:

SADD、SREM、sismember、SMEMBERS

4、使用场景:

数据不重复+可以判断一个成员是否存在+交集并集(共同关注、粉丝,两个集合求交集)。

四、HASH散列(字典):

1、介绍:

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。因为可以存储多个键值对之间的映射,就像一个微缩版的redis。和字符串一样,散列存储的值既可以是字符串也可以是数字值。

2、底层数据结构:

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。底层数据结构:hash、ziplist。

不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。

(1)触发rehash的时机:

字典类型容量变化过程叫做rehash,需要满足一定的条件才能触发扩容机制。服务器当前没有进行BGWRITEAOF或者BGSAVE命令,且当前键值对个数超过一维数组的大小,才会触发扩容。

如果当前键值对个数超过一维数组大小的五倍,无论是否在进行BGWRITEAOF或者BGSAVE命令,都会强制扩容。 Hash类型扩容后数组的长度为原来的二倍

缩容机制:如果当前键值对个数少于一维数组大小的十分之一,则触发缩容过程。缩容不会考虑当前服务器是否在进行BGWRITEAOF或者BGSAVE命令。

(2)Rehash过程

利用了两个哈希表进行的 , 有点类似数据库的迁移 , 读的时候先读旧库 , 读不到读新库 , 写的时候只写新库 ; 其他旧数据一点点的往新库上搬。

当触发扩容的时候,Redis会首先为ht[1] 分配一块内存空间。如果当前字典是一个比较大的字典,那么整个扩容过程的时间复杂度为O(n),直接完整进行扩容机制可能会导致Redis一段时间内停止服务。为了避免停止服务的情况,Redis的设计团队采用了渐进式rehash的策略,每次只对原哈希表中的一小部分进行搬迁,这样渐进式的进行,直到全部键值对都迁移到新的哈希表中。

首先,对于key的查询,我们需要到原来的哈希表中进行查找,如果找到对应的value,直接返回就可以了。如果没有找到,那么只有两种可能,一个是这个键值对已经搬迁到新的哈希表了,另外一种可能是根本就不存在这个键值对,无论是哪种可能,我们都需要再去新哈希表中对他进行查找,如果找到了就返回,如果找不到说明这个键值对不存在。

3、常用基本命令:

HSET、HGET、HGETALL、HDEL

4、常用场景:

存储对象。

五、SortedSet (ZSET)有序集合:

1、介绍:

字符串成员与浮点数分值之间的有序映射。有序集合和散列一样,都用于存储键值对,有序集合的键称为成员(member),每个成员都是各不相同的;值称为分值(score),分值必须为浮点数。有序集合是redis里面唯一一个即可用根据成员访问元素,又可以根据分值以及分值的排列顺序来访问元素的结构。

名称由来说明:关于SortedSet为什么叫ZSet?因为Set的开头字母也是S,Sorted Set缩写为SSet不合适 ,会导致命令冲突 ,所以干脆用Z代替,ZSet出生了,它的出现也是解决set天生无序的问题。

2、特点:

①可排序 ②元素不重复 ③查询速度快

(1)大多数情况下,我们使用有序集合是为了快速的判断某个元素是否存在于有序集合里面、查看某个成员在有序集合中的位置或索引、以及有序集合的某个地方快速的按范围取出多个元素。

(2)有序集合除了按照分值排序外,另外一个特性:当所有成员分值相同时,有序集合将按照名字来排序;而当所有成员的分值都是0 的时候,成员将按照字符串的二进制顺序进行排序。

3、底层数据结构 :

Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大;SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表skiplist加ziplist。

只有同时满足如下条件是,使用的是ziplist,其他时候则是使用skiplist

  • 有序集合保存的元素数量小于128个
  • 有序集合保存的所有元素的长度小于64字节

ziplist作为存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表结点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值.

当使用skiplist作为存储结构时,使用skiplist按序保存元素分值,使用dict来保存元素和分值的对应关系

ZSet数据结构类似于Set结构,只是ZSet结构中,每个元素都会有一个分值,然后所有元素按照分值的大小进行排列,相当于是一个进行了排序的链表。

如果ZSet是一个链表,而且内部元素是有序的,在进行元素插入和删除,以及查询的时候,就必须要遍历链表才行,时间复杂度就达到了O(n),这个在以单线程处理的Redis中是不能接受的。所以ZSet采用了一种跳跃表的实现。这个实现有点类似于Kafka存储消息是使用的稀疏索引。这种跳跃表的实现,其实和二分查找的思路有点接近,只是一方面因为二分查找只能适用于数组,而无法适用于链表,所以为了让链表有二分查找类似的效率,就以空间换时间来达到目的。

  • 操作时间复杂度O(logn)
  • 空间复杂度O(n)
  • 为何不用红黑树这些?:跳表实现简单,平衡树插入删除可能引发平衡调整,更加复杂,跳表只需要动动结点指针;做范围查找的时候,平衡树比skiplist操作要复杂。
  • 插入结点使用随机层数算法建立层数 每层晋升概率0.25
4、常用基本命令:

ZADD、ZRANGE、ZRANGEBYSCORE、ZREM

5、应用场景: 

(1)跳跃表因为是一个根据分数权重进行排序的列表,可以再很多场景中进行应用,比如排行榜,搜索排序等等。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_t_y_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值