Redis 数据结构使用
Redis 数据结构使用
1. String的内存空间消耗问题
1.1 String在保存数据时内存空间消耗较多
- String类型除了实际记录的数据,还需要额外的内存空间记录数据长度,空间使用等信息,这些信息被称为元数据
- 当实际保存的数据比较小的时候,元数据的空间开销就会比较大
- 当保存64位有符号整数的时候
- String类型会将其保存为一个8字节的Long类型整数
- int编码方式
- 当保存的数据中包含字符的时候
- String 用简单动态字符串 Simple Dynamic String , 共有三部分组成
- buf
- 字节数组,保存实际数据
- 为了表示字节数组的结束,会在数组最后加一个\0. 这里会额外占用一个字节的开销
- len
- 占四个字节,表示buf的已用长度
- alloc
- 占四个字节,表示buf的实际分配长度,一般来说会大于len
- buf
- 故而在上述的分析当中,len 和alloc就是元数据,带来了一部分的额外开销
- String 用简单动态字符串 Simple Dynamic String , 共有三部分组成
1.2 RedisObject 的结构
Redis本身支持多种数据类型,而不同数据类型都会有一些相同的元数据需要记录
- 最后一次访问的时间
- 被引用的次数等
因此Redis会用一个RedisObject结构体来统一记录这些元数据,同时指向实际数据
另外出于节省内存空间的考虑
当保存的是Long类型整数时,RedisObject中的指针就直接赋值为整数数据了,这样就不用额外的指针再指向整数,节省了指针的空间开销
当保存的是字符串数据,并且字符串小于等于44个字节,RedisObject中的元数据,指针的SDS是一块连续的内存区域,来避免内存碎片
当保存的数据量大于44字节的时候,SDS的数据量就会变多,Redis就不再把SDS和RedisObject布局在一起了,会给SDS分配独立的空间,并且用指针指向SDS结构
在计算总共消耗的内存的时候,值得注意的是除了使用RedisObject本身,Redis还维护了一个全局哈希表来保存所有键值对,这个结构体有三个8字节的指针,共24字节。Redis使用的是jemalloc内存分配库,会根据申请的字节数N,找一个比N大,但是最接近N的2的幂次数作为分配的空间,来减少频繁分配的次数
2. 压缩列表
压缩列表的构成
表头
- zlbytes — 列表长度
- zltail — 列表尾
- zllen — 列表entry个数
表尾
- zlend — 列表结束
表entry
- 是连续的entry
- 因为是挨着来进行放置的,所以不需要再使用额外的指针进行连接,就可以节省指针所占用的空间了
- 包括以下几部分:
- prev_len — 前一个entry的长度
- len — 自身长度 4字节
- encoding — 编码方式 1字节
- content — 保存实际数据
- 是连续的entry
Redis Hash类型底层有两种实现结构
- 压缩列表
- 哈希表
通过阈值确定应该使用哪一种来保存数据
- hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。
- hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。
3. 集合的使用
- 使用场景
- 一个key对应一个数据集合
- 比如手机app的用户登录信息 — 一天对应一系列用户ID或者移动设备ID
- 电商商品用户评价列表 — 一个商品对应一系列评论
- 在这样子的场景当中,除了记录信息,我们还需要对集合当中的数据进行统计,而我们选用的数据类型必须能够高效的统计这些数据
- 一个key对应一个数据集合
3.1 聚合统计
定义
- 指统计多个集合元素的聚合结果
例子
- 统计多个集合的共有元素
- 将两个集合相比,统计其中一个集合独有的元素
- 统计多个集合的所有元素
当你需要对多个集合进行聚合计算时,Set 类型会是一个非常不错的选择。
这里有一个潜在的风险。Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。
- 你可以从主从集群中选择一个从库,让它专门负责聚合计算
- 或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避阻塞主库实例和其他从库实例的风险了。
3.2 排序统计
- E.G
- 电商网站提供最新评论列表,需要使得集合当中的元素可以按序排列
- List和Sorted Set属于有序集合
- List按照元素进入List的顺序进行排序,而Sorted Set根据元素的权重来排序
- 电商网站提供最新评论列表,需要使得集合当中的元素可以按序排列
- 使用list来做排序的问题在于是通过元素在List当中的位置来排序,新元素插入会改变原有的顺序,都会顺次后移
- Sorted Set相对应的是根据元素的实际权重来排序和获取数据
- Sorted Set 的 ZRANGEBYSCORE 命令就可以按权重排序后返回元素。这样的话,即使集合中的元素频繁更新,Sorted Set 也能通过 ZRANGEBYSCORE 命令准确地获取到按序排列的数据。
3.3 二值状态统计
- 集合元素的取值只有0和1两种
- 可以对于数据结构进行优化,使用Bitmap
- Bitmap本身是使用String类型作为底层数据结构来实现的一种统计二值状态的数据类型,可以将其理解为一个bit数组
- 查看签到情况的基本操作
// 记录8.3 签到
SETBIT uid:sign:3000:202008 2 1
// 查询8.3是否签到
GETBIT uid:sign:3000:202008 2
// 统计该用户20年8月份的签到次数
BITCOUNT uid:sign:3000:202008
- 统计应用当中10天连续签到的用户数量
- Bitmap支持使用BITOP命令来对多个Bitmap按位做与,或,异或,的操作,操作结果会保存到一个新的bitmap当中
- 在统计 1 亿个用户连续 10 天的签到情况时,你可以把每天的日期作为 key,每个 key 对应一个 1 亿位的 Bitmap,每一个 bit 对应一个用户当天的签到情况。接下来,我们对 10 个 Bitmap 做“与”操作,得到的结果也是一个 Bitmap。在这个 Bitmap 中,只有 10 天都签到的用户对应的 bit 位上的值才会是 1。最后,我们可以用 BITCOUNT 统计下 Bitmap 中的 1 的个数,这就是连续签到 10 天的用户总数了。
3.4 基数统计
- 统计一个集合中不重复的元素个数 — 统计网页的UV
- 需要去重 — 一个用户一天内的多次访问只能算一次
- 使用set 或者hash的话都会需要将不同的id记录下来,最后看整个数据结构内元素的数量,会很大程度上占用内存
- HyperLogLog
- 用于统计基数的数据集合
- 当集合元素数量非常多的时候,计算基数所需的空间是固定的,而且比较小
- 有一点需要你注意一下,HyperLogLog 的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是 0.81%。这也就意味着,你使用 HyperLogLog 统计的 UV 是 100 万,但实际的 UV 可能是 101 万。虽然误差率不算大,但是,如果你需要精确统计结果的话,最好还是继续用 Set 或 Hash 类型。
- 用于统计基数的数据集合
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com
文章标题:Redis 数据结构使用
文章字数:2k
本文作者:Leilei Chen
发布时间:2021-02-09, 12:30:04
最后更新:2021-03-02, 12:46:14
原始链接:https://www.llchen60.com/Redis-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%BD%BF%E7%94%A8/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。