首页 > 编程知识 正文

redis提供数据结构(redis hash数据结构)

时间:2023-05-06 17:19:01 阅读:92657 作者:2749

除了常见的数据结构String、Hash、List、Set和Set (固定集)位图)之外,还将hyperloglog (超文本记录)2.8.9版添加到Redis中)、streas

String(字符串)

字符串是一组字节。 在Redis数据库中,字符串是二进制的和安全的。 这意味着它们具有已知长度,不受特殊结束字符的影响。 一个字符串最多可以存储512兆字节的内容。

虽然redis是用c语言开发的,但是c没有字符串类型,只能以指针或字符数组的形式表示一个字符串。 因此,redis设计了简单的动态字符串SDS作为基本实现。

定义SDS对象。 该对象有三个属性:

len buf已经占用的长度(表示该字符串的实际长度) free buf中未使用的缓冲区长度buf ) )字符串数据实际被保存的地方,因此取字符串长度的时间复杂度为o )1)。 另外,buf )中,仍然采用了可以直接使用c语言的c语言的标准c字符串库函数的一部分。

空间分配原则: len小于IMB(1024*1024 )时,将字符串分配空间的大小增加两倍,len为1M以上时,每次额外分配1M的空间。

这样可以获得以下特性:

redis给文字分配区域的次数在字符串的长度n以下,在原来的c语言中的分配原则一定是n。 通过减少分配次数,可以提高添加速度,占用更多内存空间,而不会自动释放这些空间。 二进制安全高效的字符串长度计算(时间复杂度为o )1) ) )高效的字符串添加操作。

Hash(舒适的牛排)

舒适的牛排是钥匙和值对的集合。 在Redis中,舒适的牛排是字符串字段和字符串值的映射。 因此,它适合于表示对象。

redis散列可以存储多个键值对之间的映射。 散列中存储的值既可以是字符串,也可以是数字。 用户还可以对存储在散列中的数字执行自递增或自递减操作。 散列可以被视为文档或关系数据库中的行。 作为hash基础的数据结构的实现有两种:

1.1个是ziplist,其结构是参照以下内容,在存储的数据超过了结构的阈值时转用hashtable的结构。 这样的转换会消耗性能,所以应该尽量避免这样的转换操作。 此结构仅在同时满足以下两个条件时使用:

密钥数量小于散列-最大压缩列表条目(默认值512 )时

如果所有值都小于散列-最大压缩列表值(默认值为64 )

每个舒适的牛排最多可保存2321亿个字段值对。

2 .另一个是hashtable。 这个结构体的时间复杂度是o(1),但是会消耗比较多的存储器空间。

List(列表)

Redis列表定义为字符串列表,按插入顺序排序。 可以将元素添加到Redis列表的开头或末尾。

由于redis支持密钥表的结构,因此它在密钥值存储世界中具有独特的特征。 列表结构可以规则地存储多个字符串,并且具有操作命令,如lpush lpop rpush rpop。 在3.2版之前,列表是使用ziplist和链接的列表实现的。 在这些旧版本中,如果列表对象同时满足以下两个条件,列表对象将使用ziplist编码:

如果列表对象中存储的所有字符串元素的长度小于64个字节,列表对象中存储的元素数量小于512个,则使用linkedlist进行转码。

从3.2版开始,重新引入了快速列表数据结构。 列表的基础都是通过快速列表实现的,它结合了ziplist和链接列表的优点。 根据原文的解释,该数据结构意味着由【A doubly linked list of ziplists】ziplist组成的双向链表。 那么,这两种数据结构是如何结合的呢?

ziplist的工作原理

由标题、n个入口节点和压缩列表的末尾标识符zlend组成的连续内存块。 然后,通过一系列的编码规则,提高内存的利用率,主要用于存储整数和相对较短的字符串。 可以看到,插入和删除元素时,必须先扩展或缩小内存,然后执行一些数据移动操作,从而降低更新效率。

链接列表的工作原理

意味着双向链表,与普通链表的定义相同。 每个条目都包含指向前方的指针,插入或删除元素时,只需要指向该元素前后的指针。 所以插入和删除很有效率。 但是,查询的效率是o(n ) [n是元素的个数]。

快速列表的工作原理

在理解上述两种数据结构的基础上,我们来看看上述“基于ziplist的双向链表”的含义。 实际上,整体上宏观上是链表结构。 但是,每个节点都以压缩列表ziplist的结构存储数据,每个ziplist可以包含多个条目。 也可以说quicklist节点存储的不是数据,而是单个数据。 总结:

总体来说,quicklist是双向链表结构,和常规链表操作一样,插入删除的效率很高,但查询的效率为o(n )。 但是,这种链表访问两端要素的时间复杂度为o(1)。 因此,对list的操作大多是poll和push。

/p>

每个quicklist节点就是一个ziplist,具备压缩列表的特性。

在redis.conf配置文件中,有两个参数可以优化列表:

list-max-ziplist-size 表示每个quicklistNode的字节大小。默认为-2 表示8KB

list-compress-depth 表示quicklistNode节点是否要压缩。默认是0 表示不压缩

列表的最大长度为232 – 1个元素(每个列表超过40亿个元素)。

Set(集合)

redis的集合和列表都可以存储多个字符串,它们之间的不同在于,列表可以存储多个相同的字符串,而集合则通过使用散列表(hashtable)来保证自已存储的每个字符串都是各不相同的(这些散列表只有键,但没有与键相关联的值),redis中的集合是无序的。还可能存在另一种集合,那就是intset,它是用于存储整数的有序集合,里面存放同一类型的整数。共有三种整数:int16_t、int32_t、int64_t。查找的时间复杂度为O(logN),但是插入的时候,有可能会涉及到升级(比如:原来是int16_t的集合,当插入int32_t的整数的时候就会为每个元素升级为int32_t)这时候会对内存重新分配,所以此时的时间复杂度就是O(N)级别的了。注意:intset只支持升级不支持降级操作。

intset在redis.conf中也有一个配置参数set-max-intset-entries默认值为512。表示如果entry的个数小于此值,则可以编码成REDIS_ENCODING_INTSET类型存储,节约内存。否则采用dict(字典)的形式存储。

在Redis中,添加,删除和查找的时间复杂度是O(1),集合中的最大成员数为2^32 – 1个元素(每个列表超过40亿个元素)。

Sorted Set(有序集合)

Redis有序集合类似于Redis集合,也是一组非重复的字符串集合。但是,排序集的每个成员都与一个分数相关联,该分数用于获取从最小到最高分数的有序排序集。虽然成员是独特的,但可以重复分数。

有序集合和散列一样,都用于存储键值对:有序集合的键被称为成员(member),每个成员都是各不相同的。有序集合的值则被称为分值(score),分值必须为浮点数。有序集合是redis里面唯一一个既可以根据成员访问元素(这一点和散列一样),又可以根据分值以及分值的排列顺序访问元素的结构。它的存储方式也有两种:

是ziplist结构。

与上面的hash中的ziplist类似,member和score顺序存放并按score的顺序排列

另一种是skiplist与dict的结合。

skiplist是一种跳跃表结构,用于有序集合中快速查找,大多数情况下它的效率与平衡树差不多,但比平衡树实现简单。redis的作者对普通的跳跃表进行了修改,包括添加spantailbackward指针、score的值可重复这些设计,从而实现排序功能和反向遍历的功能。

一般跳跃表的实现,主要包含以下几个部分:

表头(head):指向头节点表尾(tail):指向尾节点节点(node):实际保存的元素节点,每个节点可以有多层,层数是在创建此节点的时候随机生成的一个数值,而且每一层都是一个指向后面某个节点的指针。层(level):目前表内节点的最大层数长度(length):节点的数量。

跳跃表的遍历总是从高层开始,然后随着元素值范围的缩小,慢慢降低到低层。

跳跃表的原理请参考我前面的文章:数据结构与算法 -- 跳跃表(SkipList)

前面也说了,有序列表是使用skiplist和dict结合实现的,skiplist用来保障有序性和访问查找性能,dict就用来存储元素信息,并且dict的访问时间复杂度为O(1)。

Bit(位图)

位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是 byte 数组。我们可以使用普通的 get/set 直接获取和设置整个位图的内容,也可以使用位图操作 getbit/setbit等将 byte 数组看成「位数组」来处理。

Redis 的位数组是自动扩展,如果设置了某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充。

HyperLogLog

HyperLogLog算法经常在数据库中被用来统计某一字段的Distinct Value(即不重复值,下文简称DV)

HyperLogLog算法来源于论文《HyperLogLog the analysis of a near-optimal cardinality estimation algorithm》,可以使用固定大小的字节计算任意大小的DV,如果你感兴趣可以试着百度一下,本文不做深究,只做简单介绍

基数就是指一个集合中不同值的数目,比如[a,b,c,d]的基数就是4,[a,b,c,d,a]的基数还是4,因为a重复了一个,不算。基数也可以称之为Distinct Value,简称DV。HyperLogLog算法就是用来计算基数的。

Stream

stream是一个看起来比pubsub可靠多的消息队列。pubsub不靠谱? 很不靠谱,网络一断或buffer一大就会主动清理数据。stream的设计参考了kafka的消费组模型

用过kafka的人再看这图就很好理解了。源数据用来存放stream的生产出来的数据,数据是按照有seq_id序排列的。 redis会记录stream里每个消费组最后消费的last_id及还没有返回ack确认的id数据。每个消费组都有一个last_id, 也就是说 每个消费组都可以消费同一条数据,每个消费组里可以有多个消费者,多个消费组的关系是相互竞争的。

比如同一条数据,a系统会用,b系统也会用到,那么这时候要用消费组了。 a系统如果只有一个消费者会造成吞吐不够的情况,redis stream consumer group可以在同一个消费组里有多个消费者,consumer消费者多了,吞吐自己就上来了。 redis stream是可以保证这些操作原子性的。

stream又维护了一个pending_ids的数据,他的作用是维护消费者的未确认的id,比如消费者get了数据,但是返回给你的时候网络异常了,crash了,又比如消费者收到了,但是crash掉了? redis stream维护了这些没有xack的id. 我们可以xpending来遍历这些数据,xpending是有时间信息的,我们可以在代码层过滤一个小时之前还未xack的id。

至于底层细节,我们后续慢慢给大家解开它的面纱!本文点到为止。

适用场景分析

String,redis对于KV的操作效率很高,可以直接用作计数器。例如,统计在线人数等等,另外string类型是二进制存储安全的,所以也可以使用它来存储图片,甚至是视频等。hash,存放键值对,一般可以用来存某个对象的基本属性信息,例如,用户信息,商品信息等,另外,由于hash的大小在小于配置的大小的时候使用的是ziplist结构,比较节约内存,所以针对大量的数据存储可以考虑使用hash来分段存储来达到压缩数据量,节约内存的目的,例如,对于大批量的商品对应的图片地址名称。比如:商品编码固定是10位,可以选取前7位做为hash的key,后三位作为field,图片地址作为value。这样每个hash表都不超过999个,只要把redis.conf中的hash-max-ziplist-entries改为1024,即可。list,列表类型,可以用于实现消息队列,也可以使用它提供的range命令,做分页查询功能。set,集合,整数的有序列表可以直接使用set。可以用作某些去重功能,例如用户名不能重复等,另外,还可以对集合进行交集,并集操作,来查找某些元素的共同点zset,有序集合,可以使用范围查找,排行榜功能或者topN功能。位图,可以用来实现签到功能Stream,消息队列HyperLogLog,统计基数的场景都适用

OK,篇幅不宜过长,谢谢你的浏览!!喜欢就关注一下吧。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。