redis数据类型
1.常见基础数据类型
1.图片信息存储。
我们要开发一个图片存储系统,要求这个系统能快速地记录图片 ID 和图片在存储系统中保存时的 ID(可以直接叫作图片存储对象 ID)。同时,还要能够根据图片 ID 快速查找到图片存储对象 ID。因为图片数量巨大,所以我们就用 10 位数来表示图片 ID 和图片存储对象 ID,例如,图片 ID 为 1101000051,它在存储系统中对应的 ID 号是 3301000051。
- string类型保存数据时消耗的内存空间较多
我们保存1亿张图片的信息,用了月6.4GB的内存,一个图片ID和图片存储对象id的记录平均用64字节。图片id和图片存储对象id都是10位数,我们可以用2个8字节的long类型表示这两个id,因为8字节的long类型最大可以表示2^64,所以肯定可以表示10位数,为什么,string类型却用了64字节?
除了记录实际数据,string类型还需要额外的内存空间记录数据长度,空间使用等信息,也叫做元数据。当保存数据较小时,元数据的空间开销就显得比较大。每个int编码的redisobject部分占8字节,指针部分直接被赋值为8字节的整数了,此时,每个id16字节,共32字节,redis会使用一个全局哈希表保存所有键值对,哈希表的每一项时一个dictEntry的结构体,用来指向键值对,结构体中有三个8字节的指针,分别指向key,value,以及下一个dictEntry,三个指针共24字节,但是分配空间,会根据申请的字节数N,找一个比N大,但是最近N的2的幂次数作为分配空间。所有会分配32
-
redis有一种底层数据结构,叫做压缩列表
表头有三个字段zlbytes,ztail和zlen,分布表示列表长度列表尾的偏移量,以及列表中的entry个数,压缩列表表尾还有一个zlend,表示列表结束。
每个entry的元数据包括下面几部分:
1.prev_len表示前一个entry的长度。prev_len有2种取值情况:1字节或5字节,取值为1字节时,表示上一个entry的长度小于254字节,否则5字节
2.len表示自身长度4字节
3.encoding表示编码方式1字节
4.content保存实际数据
每个 entry 保存一个图片存储对象 ID(8 字节),此时,每个 entry 的 prev_len 只需要 1 个字节就行,因为每个 entry 的前一个 entry 长度都只有 8 字节,小于 254 字节。这样一来,一个图片的存储对象 ID 所占用的内存大小是 14 字节(1+4+1+8=14),实际分配 16 字节。
Redis 基于压缩列表实现了 List、Hash 和 Sorted Set 这样的集合类型,这样做的最大好处就是节省了 dictEntry 的开销。当你用 String 类型时,一个键值对就有一个 dictEntry,要用 32 字节空间。但采用集合类型时,一个 key 就对应一个集合的数据,能保存的数据多了很多,但也只用了一个 dictEntry,这样就节省了内存。 -
如何用存储类型保存单值的键值对?
可以采用基于hash类型的二级编码方法,这里说的二级编码,就是把一个单值的数据拆分成两部分,前一部分作为 Hash 集合的 key,后一部分作为 Hash 集合的 value,这样一来,我们就可以把单值数据保存到 Hash 集合中了。以图片 ID 1101000060 和图片存储对象 ID 3302000080 为例,我们可以把图片 ID 的前 7 位(1101000)作为 Hash 类型的键,把图片 ID 的最后 3 位(060)和图片存储对象 ID 分别作为 Hash 类型值中的 key 和 value。按照这种设计方法,我在 Redis 中插入了一组图片 ID 及其存储对象 ID 的记录,并且用 info 命令查看了内存开销,我发现,增加一条记录后,内存占用只增加了 16 字节。 -
Hash 类型底层结构什么时候使用压缩列表,什么时候使用哈希表呢?
Hash 类型设置了用压缩列表保存数据时的两个阈值,一旦超过了阈值,Hash 类型就会用哈希表来保存数据了。这两个阈值分别对应以下两个配置项:hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。如果我们往 Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构由压缩列表转为哈希表。一旦从压缩列表转为了哈希表,Hash 类型就会一直用哈希表进行保存,而不会再转回压缩列表了。在节省内存空间方面,哈希表就没有压缩列表那么高效了。
为了能充分使用压缩列表的精简内存布局,我们一般要控制保存在 Hash 集合中的元素个数。所以,在刚才的二级编码中,我们只用图片 ID 最后 3 位作为 Hash 集合的 key,也就保证了 Hash 集合的元素个数不超过 1000,同时,我们把 hash-max-ziplist-entries 设置为 1000,这样一来,Hash 集合就可以一直使用压缩列表来节省内存空间了。‘
2.有一亿个keys要统计,用哪种集合
在 Web 和移动应用的业务场景中,我们经常需要保存这样一种信息:一个 key 对应了一个数据集合。我举几个例子。
手机 App 中的每天的用户登录信息:一天对应一系列用户 ID 或移动设备 ID;
电商网站上商品的用户评论列表:一个商品对应了一系列的评论;
用户在手机 App 上的签到打卡信息:一天对应一系列用户的签到记录;
应用网站上的网页访问信息:一个网页对应一系列的访问点击。
- 聚合统计
所谓的聚合统计,就是指统计多个集合元素的聚合结果,包括:统计多个集合的共有元素(交集统计);把两个集合相比,统计其中一个集合独有的元素(差集统计);统计多个集合的所有元素(并集统计)。
要完成这个统计任务,我们可以用一个集合记录所有登录过 App 的用户 ID,同时,用另一个集合记录每一天登录过 App 的用户 ID。然后,再对这两个集合做聚合统计。
- 排序统计
最新评论列表包含了所有评论中的最新留言,这就要求集合类型能对元素保序,也就是说,集合中的元素可以按序排列,这种对元素保序的集合类型叫作有序集合。List 和 Sorted Set 就属于有序集合。
List 是按照元素进入 List 的顺序进行排序的,而 Sorted Set 可以根据元素的权重来排序。用set的话,
List 是通过元素在 List 中的位置来排序的,当有一个新元素插入时,原先的元素在 List 中的位置都后移了一位,比如说原来在第 1 位的元素现在排在了第 2 位。所以,对比新元素插入前后,List 相同位置上的元素就会发生变化,用 LRANGE 读取时,就会读到旧元素。
在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议你优先考虑使用 Sorted Set。
- 二值状态统计
在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。
在签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。这个时候,我们就可以选择 Bitmap。这是 Redis 提供的扩展数据类型。
Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态。你可以把 Bitmap 看作是一个 bit 数组。
Bitmap 提供了 GETBIT/SETBIT 操作,使用一个偏移值 offset 对 bit 数组的某一个 bit 位进行读和写。不过,需要注意的是,Bitmap 的偏移量是从 0 开始算的,也就是说 offset