八大数据类型底层结构及用途
文章目录
string 字符串类型
先上图镇场子
String 是一组字节。在 Redis 数据库中,字符串是二进制安全的。这意味着它们具有已知长度,并且不受任何特殊终止字符的影响。可以在一个字符串中存储最多 512 兆字节的内容。
-
tring 是 Redis 的最基本数据类型。可以把它理解为 Mc 中 key 对应的 value 类型。
string 类型是二进制安全的
,即 string 中可以包含任何数据。 -
Redis 中的普通 string 采用
raw encoding 即原始编码方式
,该编码方式会动态扩容,并通过提前预分配冗余空间,来减少内存频繁分配的开销。 -
在字符串长度小于 1MB 时,按所需长度的 2 倍来分配,超过 1MB,则按照每次额外增加 1MB 的容量来预分配。
-
Redis 中的数字也存为 string 类型,但编码方式跟普通 string 不同,数字采用整型编码,字符串内容直接设为整数值的二进制字节序列。
底层数据结构
Redis 是用 C 语言写的,但是对于Redis的字符串,却不是 C 语言中的字符串(即以空字符’\0’结尾的字符数组),它是自己构建了一种名为 简单动态字符串
(simple dynamic string,SDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示;SDS除了用于实现字符串类型,还被用作AOF持久化时的缓冲区。
使用场景
缓存结构体信息
将结构体json序列化成字符串
,然后将字符串
保存在redis的value
中,将结构体的业务唯一标示作为key;这种保存json的用法用的最多的场景就是缓存用户信息
,将用户bean信息
转成json
再序列化
为字符串
作为value
保存在redis中,将用户id
作为key
。从代码中获取用户缓存信息就是一个逆过程,根据userid作为key获取到结构体json,然后将json转成java bean
基本操作
127.0.0.1:6379> set user.10001 {“id”:”10001”,”name”:”monkey”}
(integer) 1
计数功能
- 我们都知道
redis是单线程模式
,并且redis将很多常用的事务操作
进行了封装
,这里我们最常用的就是数值自增
或自减
,redis的作者封装了incr
可以进行自增,没调用一次自增1,因为redis是单线程运行,所以就算client是多线程调用那么也是正确自增,因为incr命令中将read和write做了事务封装。同样可以设置incr的step,每次根据step进行自增,当然如果要达到自减的效果,那么只要将step设置为负数就可以了* - 计数功能使用的场景很多,我们之前经常用在实时计数统计场景,也用在过库存场景、限流计数场景等等,而且redis的性能也是非常高的,对于一般的并发量没那么高的系统都是适用的
操作方式
127.0.0.1:6379> set num 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incrby num 2
(integer) 4
// 同理 自减也是一样的
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误 ERR ERR hash value is not an integer。
返回值为 自增或自减之后的值
list 列表类型
Redis 的 list 列表,是一个快速双向链表
,存储了一系列的 string 类型的字串值
。list 中的元素按照插入顺序排列
。插入元素的方式,可以通过lpush
将一个或多个元素插入到列表的头部
,也可以通过rpush 将一个或多个元素插入到队列尾部
,还可以通过lset、linsert
将元素插入到指定位置
或指定元素
的前后
列表可以包含超过 40 亿 个元素 ( 2^32 - 1 )
底层数据结构
redis list数据结构底层采用压缩列表ziplist
或linkedlist
两种
数据结构进行存储,首先以ziplist
进行存储,在不满足ziplist的存储要求后转换为linkedlist
列表。
当列表对象同时满足以下两个条件时,列表对象使用ziplist进行存储,否则用linkedlist存储
- 列表对象保存的所有字符串元素的
长度小于64字节
- 列表对象保存的
元素数量小于512个
使用场景
消息队列
:reids的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。文章列表或者数据分页展示的应用
。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。
set 集合类型
- set 是
string 类型的无序集合
,set 中的元素
是唯一
的,即 set 中不会出现重复的元素。Redis 中的集合一般是通过dict 哈希表
实现的,所以插入、删除,以及查询元素,可以根据元素 hash
值直接定位,时间复杂度为 O(1)。
对 set 类型数据的操作,除了常规的添加、删除、查找元素外,还可以用以下指令对 set 进行操作。
- sismember 指令判断该 key 对应的 set 数据结构中,是否存在某个元素,如果存在返回 1,否则返回 0;
- sdiff 指令来对多个 set 集合执行差集;
- sinter 指令对多个集合执行交集;
- sunion 指令对多个集合执行并集;
- spop 指令弹出一个随机元素;
- srandmember 指令返回一个或多个随机元素
集合中最大的成员数为 2^32 – 1 (4294967295), 每个集合可存储 40 多亿个成员
底层结构
老规矩先上图
redis的集合对象set的底层存储结构特别神奇,我估计一般人想象不到,底层使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。是不是觉得用hashtable存储set是一件很神奇的事情。
set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:
- 集合对象保存的
所有元素都是整数值
- 集合对象保存的元素数量
不超过512个
redis set存储过程
以set的sadd命令为例子,整个添加过程如下:
- 检查set是否存在不存在则创建一个set结合。
- 根据传入的set集合一个个进行添加,添加的时候需要进行内存压缩。
- setTypeAdd执行set添加过程中会判断是否进行编码转换
稍微深入分析一下set的单个元素的添加过程,首先如果已经是hashtable的编码,那么我们就走正常的hashtable的元素添加,如果原来是intset的情况,那么我们就需要进行如下判断:
- 如果能够转成int的对象(isObjectRepresentableAsLongLong),那么就用intset保存。
- 如果用intset保存的时候,如果长度超过512(REDIS_SET_MAX_INTSET_ENTRIES)就转为hashtable编码。
- 其他情况统一用hashtable进行存储。
使用场景
redis set是集合类型的数据结构,那么集合类型就比较适合用于聚合分类。
- 标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。
- 共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。
- 统计网站的独立IP。利用set集合当中元素不唯一性,可以快速实时统计访问网站的独立IP。
sorted set 有序集合类型
Redis 中的 sorted set 有序集合
也称为 zset
,有序集合同 set 集合类似,也是 string 类型元素的集合,且所有元素不允许重复
。
- 不同的是每个元素都会关联一个 double 类型的分数。Redis 正是通过分数来为集合中的成员进行从小到大的排序。
- 有序集合的成员是唯一的,但分数 ( score ) 却可以重复。
- 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
- 集合中最大的成员数为 232 – 1 ( 4294967295 ) , 每个集合可存储 40 多亿个成员
底层数据结构
zset底层的存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist,两个条件如下
-
有序集合保存的元素数量小于128个
-
有序集合保存的所有元素的长度小于64字节
当ziplist
作为zset
的底层存储结构时候,每个集合元素
使用两个
紧挨在一起的压缩列表
节点
来保存,第一个
节点保存
元素的成员,第二个元素
保存元素的分值
。
-
当
skiplist
作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值
,使用dict
来保存元素和分值
的映射关系
使用场景
有序集合的使用场景与集合类似,但是set集合不是自动有序的,而sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时,就可以选择sorted set数据结构作为选择方案
- 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
- 用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
hash 类型
Redis 中的哈希实际是 field 和 value 的一个映射表。
- hash 数据结构的特点是在单个 key 对应的哈希结构内部,可以记录多个键值对,即 field 和 value 对,value 可以是任何字符串。而且这些键值对查询和修改很高效。
- 所以可以用 hash 来存储具有多个元素的复杂对象,然后分别修改或获取这些元素。hash 结构中的一些重要指令,包括:hmset、hmget、hexists、hgetall、hincrby 等
hmset 指令批量插入多个 field、value 映射;
hmget 指令获取多个 field 对应的 value 值;
hexists 指令判断某个 field 是否存在;
如果 field 对应的 value 是整数,还可以用 hincrby 来对该 value 进行修改
底层数据结构
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个
bitmap 位图类型
Redis 中的 bitmap 位图是一串连续的二进制数字,底层实际是基于 string 进行封装存储的,按 bit 位进行指令操作的。bitmap 中每一 bit 位所在的位置就是 offset 偏移,可以用 setbit、bitfield 对 bitmap 中每个 bit 进行置 0 或置 1 操作,也可以用 bitcount 来统计 bitmap 中的被置 1 的 bit 数,还可以用 bitop 来对多个 bitmap 进行求与、或、异或等操作。
bitmap 位图的特点是按位设置、求与、求或等操作很高效,而且存储成本非常低,用来存对象标签属性的话,一个 bit 即可存一个标签。可以用 bitmap,存用户最近 N 天的登录情况,每天用 1 bit,登录则置 1。个性推荐在社交应用中非常重要,可以对新闻、feed 设置一系列标签,如军事、娱乐、视频、图片、文字等,用 bitmap 来存储这些标签,在对应标签 bit 位上置 1。对用户,也可以采用类似方式,记录用户的多种属性,并可以很方便的根据标签来进行多维度统计。bitmap 位图的重要指令包括:setbit、 getbit、bitcount、bitfield、 bitop、bitpos 等。
在移动社交时代,LBS 应用越来越多,比如微信、陌陌中附近的人,美团、大众点评中附近的美食、电影院,滴滴、优步中附近的专车等。要实现这些功能,就得使用地理位置信息进行搜索。地球的地理位置是使用二维的经纬度进行表示的,我们只要确定一个点的经纬度,就可以确认它在地球的位置。
Redis 在 3.2 版本之后增加了对 GEO 地理位置的处理功能。Redis 的 GEO 地理位置本质上是基于 sorted set 封装实现的。在存储分类 key 下的地理位置信息时,需要对该分类 key 构建一个 sorted set 作为内部存储结构,用于存储一系列位置点。
Redis 的 GEO 地理位置数据结构,应用场景很多,比如查询某个地方的具体位置,查当前位置到目的地的距离,查附近的人、餐厅、电影院等。GEO 地理位置数据结构中,重要指令包括 geoadd、geopos、geodist、georadius、georadiusbymember 等
HyperLogLog 基数统计类型
Redis 的 hyperLogLog 是用来做基数统计的数据类型,当输入巨大数量的元素做统计时,只需要很小的内存即可完成。HyperLogLog 不保存元数据,只记录待统计元素的估算数量,这个估算数量是一个带有 0.81% 标准差的近似值,在大多数业务场景,对海量数据,不足 1% 的误差是可以接受的。
Redis 的 HyperLogLog 在统计时,如果计数数量不大,采用稀疏矩阵存储,随着计数的增加,稀疏矩阵占用的空间也会逐渐增加,当超过阀值后,则改为稠密矩阵,稠密矩阵占用的空间是固定的,约为12KB字节。
通过 hyperLoglog 数据类型,你可以利用 pfadd 向基数统计中增加新的元素,可以用 pfcount 获得 hyperLogLog 结构中存储的近似基数数量,还可以用 hypermerge 将多个 hyperLogLog 合并为一个 hyperLogLog 结构,从而可以方便的获取合并后的基数数量。
hyperLogLog 的特点是统计过程不记录独立元素,占用内存非常少,非常适合统计海量数据。在大中型系统中,统计每日、每月的 UV 即独立访客数,或者统计海量用户搜索的独立词条数,都可以用 hyperLogLog 数据类型来进行处理。
geo 地理位置类型;
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
Redis GEO 操作方法有:
- geoadd:添加地理位置的坐标。
- geopos:获取地理位置的坐标。
- geodist:计算两个位置之间的距离。
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash:返回一个或多个位置对象的 geohash 值。
geoadd
geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。