首页 > 编程知识 正文

redis高级数据类型,redis5种数据类型

时间:2023-05-04 11:07:38 阅读:33451 作者:4371

90%的人知道Redis 5的五个最基本的数据结构;

只有不到10%的人知道8个基本的数据结构,5个基本的位图地理位置散列超文本日志

只有不到5%的人知道9种基本数据结构,5.0最新版本的数据结构流

只有不到1%的人掌握了所有9种基本数据结构和8种内部代码

掌握这篇文章的知识点,成为面试官眼中Redis方面最美的孩子!

说明:本文基于Redis-3.2.11版本源代码进行分析。

所谓5种普通的数据结构我什么都没说,但对Redis稍有了解的人知道5种最基本的数据结构。 是String、List、Hash、Set和sorted集。 但是,必须注意的是,这里仍然存在一些高频问题。

Set与Hash关系的答案是,Set为特殊的value为空Hash。 Set型操作的源代码位于tset.c上。 添加元素(intsettypeadd(robj*subject,sds value ) )后,如果编码类型为OBJENCODING_HT,则新源代码的源代码如下所示

同样,当在tHash.c中看到hash型新元素时,如果确定代码类型为OBJENCODING_HT,则调用dictAdd(o-ptr,f,v ),dictadd最终也是DDD

因此,Redis中Set和Hash的关系很明显。 如果编码为OBJENCODINGHT,则两者都是dict数据类型,但Set是value为NULL的特殊dict。

介绍Sorted Set,说明Sorted Set的数据结构是名为SkipList的跳转表。 如下图所示,红线是寻找10的过程。

如何使用Sorted set实现多维排序默认情况下,Sorted set只能基于一个因子score进行排序。 那样的话,极限就会变大。 举个板栗吧。 热门排行榜必须按下载数量的最近更新时间进行排序。 这就像数据库中的按订单下载,更新时间磁盘。 这样的需求用Redis的Sorted Set来实现怎么样?

其实很简单。 想法是用一种方式将排序中涉及的多个维列转换为特殊列。 即result=function(x,y,z )。 也就是说,x、y、z是下载量、时间等3个排序因子,用自定义函数function ) )计算得到result,将result

Redis的内部编码是常说的String、List、Hash、Set、Sorted Set是对外编码,实际上每个数据结构都有自己的底层内部编码实现,是多个实现,所以Redis是适当的

如下图所示,修改“intset代码而不是inset代码”后,可以看到每个数据结构中安装了两种以上的内部代码。 例如,字符串数据结构包含三种类型的内部代码: raw、int和embstr。 另外,一些内部编码可以实现为各种外部数据结构的内部。 例如,ziplist是hash、list和zset共有的内部编码,而set的内部编码是hashtable或intset。

像Redis这样的设计有两个优点。

1 .可以在不影响对外数据结构和指令的情况下偷偷改进内部代码。 这样,一旦开发出更好的内部代码,就无需更改对外的数据结构和指令。

2 .多种内部编码的实现可以在不同的场景中发挥各自的优势。 例如,ziplist节省内存,但列表元素过多会降低性能。 此时,Redis会根据配置选项将列表类型的内部实现转换为链接列表。

String的3种内部代码从上图可以看出,String的3种内部代码分别为int、embstr、raw。 我很清楚int型。 如果某个key的value是整数,则Redis将其编码为int类型。 此外,如果将此value视为字符串,则它的长度不能超过20。 如下所示。 这种编码类型是为了节约内存。 默认情况下,Redis缓存10000个整数值#define OBJSHAREDINTEGERS 10000 )。 也就是说,如果有10个不同的KEY,则所有value的值都在10000以内,实际上全部共享同一对象。

其次,ebmstr和raw的内部代码长度有限。 请看下面的源代码。

也就是说,embstr和raw码的长度极限为44,可以验证如下。 长度超过44时,为raw编码类型,没有优化。 如果是多长,会消耗多少内存:

那么为什么会有embstr代码呢? 与raw相比的优势在哪里? embstr编码将创建字符串对象所需的空间分配次数从原始编码的两次减少到一次。 因为用embstr编码的字符串对象的所有数据都存储在一个链接中

续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能更好地利用缓存带来的优势。并且释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码对象的字符串对象需要调用两次内存释放函数。如下图所示,左边是embstr编码,右边是raw编码:

ziplist

由前面的图可知,List,Hash,Sorted Set三种对外结构,在特殊情况下的内部编码都是ziplist,那么这个ziplist有什么神奇之处呢?

以Hash为例,我们首先看一下什么条件下它的内部编码是ziplist:

当忧心的香氛类型元素个数小于hash-max-ziplist-entries配置(默认512个);

所有值都小于hash-max-ziplist-value配置(默认64个字节);

如果是sorted set的话,同样需要满足两个条件:

元素个数小于zset-max-ziplist-entries配置,默认128;

所有值都小于zset-max-ziplist-value配置,默认64。

实际上,ziplist充分体现了Redis对于存储效率的追求。一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist却是将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。它是一个表(list),但其实不是一个链表(linked list)。

ziplist的源码在ziplist.c这个文件中,其中有一段这样的描述 – The general layout of the ziplist is as follows::

<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

zlbytes:表示这个ziplist占用了多少空间,或者说占了多少字节,这其中包括了zlbytes本身占用的4个字节;

zltail:表示到ziplist中最后一个元素的偏移量,有了这个值,pop操作的时间复杂度就是O(1)了,即不需要遍历整个ziplist;

zllen:表示ziplist中有多少个entry,即保存了多少个元素。由于这个字段占用16个字节,所以最大值是216-1,也就意味着,如果entry的数量超过216-1时,需要遍历整个ziplist才知道entry的数量;

entry:真正保存的数据,有它自己的编码;

zlend:专门用来表示ziplist尾部的特殊字符,占用8个字节,值固定为255,即8个字节每一位都是1。

如下就是一个真实的ziplist编码,包含了2和5两个元素:

linkedlist

这是List的一种编码数据结构非常简单,就是我们非常熟悉的双向链表,对应Java中的LinkedList。

skiplist

这个前面也已经提及,就是经典的跳表数据结构。

hashtable

这个也很容易,对应Java中的HashMap。

intset

Set特殊内部编码,当满足下面的条件时Set的内部编码就是intset而不是hashtable:

1.Set集合中必须是64位有符号的十进制整型;2.元素个数不能超过set-max-intset-entries配置,默认512;

验证如下:

那么intset编码到底是个什么东西呢?看它的源码定义如下,很明显,就是整型数组,并且是一个有序的整型数组。它在内存分配上与ziplist有些类似,是连续的一整块内存空间,而且对于大整数和小整数采取了不同的编码,尽量对内存的使用进行了优化。这样的数据结构,如果执行SISMEMBER命令,即查看某个元素是否在集合中时,事实上使用的是二分查找法:

3种高级数据结构

Redis中3种高级数据结构分别是bitmapGEOHyperLogLog 其中,最重要的就是bitmap。

bitmap

这个就是Redis实现的BloomFilter,BloomFilter非常简单,如下图所示,假设已经有3个元素a、b和c,分别通过3个hash算法h1()、h2()和h2()计算然后对一个bit进行赋值,接下来假设需要判断d是否已经存在,那么也需要使用3个hash算法h1()、h2()和h2()对d进行计算,然后得到3个bit的值,恰好这3个bit的值为1,这就能够说明:d可能存在集合中。再判断e,由于h1(e)算出来的bit之前的值是0,那么说明:e一定不存在集合中:

需要说明的是,bitmap并不是一种真实的数据结构,它本质上是String数据结构,只不过操作的粒度变成了位,即bit。因为String类型最大长度为512MB,所以bitmap最多可以存储2^32个bit。

GEO
GEO数据结构可以在Redis中存储地理坐标,并且坐标有限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:

1.有效的经度从-180度到180度。 2.有效的纬度从-85.05112878度到85.05112878度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。添加地理位置命令如下:

但是,需要说明的是,Geo本身不是一种数据结构,它本质上还是借助于Sorted Set(ZSET),并且使用GeoHash技术进行填充。Redis中将经纬度使用52位的整数进行编码,放进zset中,score就是GeoHash的52位整数值。在使用Redis进行Geo查询时,其内部对应的操作其实就是zset(skiplist)的操作。通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标。

总之,Redis中处理这些地理位置坐标点的思想是:二维平面坐标点 --> 一维整数编码值 --> zset(score为编码值) --> zrangebyrank(获取score相近的元素)、zrangebyscore --> 通过score(整数编码值)反解坐标点 --> 附近点的地理位置坐标。

GEOHASH原理

使用wiki上的例子,纬度为42.6,经度为-5.6的点,转化为base32的话要如何转呢?首先拿纬度来进行说明,纬度的范围为-90到90,将这个范围划为两段,则为[-90,0]、[0,90],然后看给定的纬度在哪个范围,在前面的范围的话,就设当前位为0,后面的话值便为1.然后继续将确定的范围1分为2,继续以确定值在前段还是后段来确定bit的值。就这样慢慢的缩小范围,一般最多缩小13次就可以了(经纬度的二进制位相加最多25位,经度13位,纬度12位)。这时的中间值,将跟给定的值最相近。如下图所示:

第1行,纬度42.6位于[0, 90]之间,所以bit=1;第2行,纬度42.6位于[0, 45]之间,所以bit=0;第3行,纬度42.6位于[22.5, 45]之间,所以bit=1,以此类推。这样,取出图中的bit位:1011 1100 1001,同样的方法,将经度(范围-180到180)算出来为 :0111 1100 0000 0。结果对其如下:

# 经度0111 1100 0000 0# 纬度1011 1100 1001

得到了经纬度的二进制位后,下面需要将两者进行结合:从经度、纬度的循环,每次取其二进制的一位(不足位取0),合并为新的二进制数:01101111 11110000 01000001 0。每5位为一个十进制数,结合base32对应表映射为base32值为:ezs42。这样就完成了encode的过程。

Streams

这是Redis5.0引入的全新数据结构,用一句话概括Streams就是Redis实现的内存版kafka。而且,Streams也有Consumer Groups的概念。通过Redis源码中对stream的定义我们可知,streams底层的数据结构是radix tree:

那么这个radix tree长啥样呢?在Redis源码的rax.h文件中有一段这样的描述,这样看起来是不是就比较直观了:


Radix Tree(基数树) 事实上就几乎相同是传统的二叉树。仅仅是在寻找方式上,以一个unsigned int类型数为例,利用这个数的每个比特位作为树节点的推断。能够这样说,比方一个数10001010101010110101010,那么依照Radix 树的插入就是在根节点,假设遇到0,就指向左节点,假设遇到1就指向右节点,在插入过程中构造树节点,在删除过程中删除树节点。如下是一个保存了7个单词的Radix Tree:

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