在实际开发, Redis 使用会频繁,那么在使用过程中我们该如何正确抉择数据类型呢?哪些场景下适用哪些数据类型。而且在面试中也很常会被面试官问到Redis数据结构方面的问题:
Redis为什么快呢? 为什么查询操作会变慢了? Redis Hash rehash过程 为什么使用哈希表作为Redis的索引当我们分析理解了 Redis 数据结构,可以为了我们在使用 Redis 的时候,正确抉择数据类型使用,提升系统性能。【相关推荐:Redis视频教程】
Redis 底层数据结构
Redis 是一个 内存 键值 key-value 数据库,且键值对数据保存在 内存 中,因此 Redis 基于内存的数据操作,其效率高,速度快;
其中, Key 是 String 类型, Redis 支持的 value 类型包括了 String 、 List 、 Hash 、 Set 、 Sorted Set 、 BitMap 等。 Redis 能够之所以能够广泛地适用众多的业务场景,基于其多样化类型的 value 。
而 Redis 的 Value 的数据类型是基于为 Redis 自定义的对象系统 redisObject 实现的,
typedef struct redisObject{ //类型 unsigned type:4; //编码 unsigned encoding:4; //指向底层实现数据结构的指针 void *ptr; ….. }
特点:当数据量很大时,跳表的查找复杂度为O(logN)。
综上所述,可以得知底层数据结构的时间复杂度:
数据结构类型 时间复杂度 哈希表 O(1) 整数数组 O(N) 双向链表 O(N) 压缩列表 O(N) 跳表 O(logN)Redis 自定义的对象系统类型即为 Redis 的 Value 的数据类型, Redis 的数据类型是基于底层数据结构实现的,那数据类型有哪些呢?
Redis数据类型
String 、 List 、 Hash 、 Sorted Set 、 Set 比较常见的类型,其与底层数据结构对应关系如下:
数据类型 数据结构 String SDS(简单动态字符串) List 双向链表压缩列表 Hash 压缩列表<br/>哈希表 Sorted Set 压缩列表<br/>跳表 Set 哈希表<br/>整数数组
数据类型对应特点跟其实现的底层数据结构差不多,性质也是一样的,且
String ,基于SDS实现,适用于简单 key-value 存储、 setnx key value 实现分布式锁、计数器(原子性)、分布式全局唯一ID。
List , 按照元素进入 List 的顺序进行排序的,遵循FIFO(先进先出)规则,一般使用在 排序统计以及简单的消息队列。
Hash , 是字符串 key 和字符串 value 之间的映射,十分适合用来表示一个对象信息 ,特点添加和删除操作复杂度都是O(1)。
Set ,是 String 类型元素的无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据。 基于哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
Sorted Set , 是 Set 的类型的升级, 不同的是每个元素都会关联一个 double 类型的分数,通过分数排序,可以范围查询。
那我们再来看看这些数据类型, Redis Geo 、 HyperLogLog 、 BitMap ?
Redis Geo , 将地球看作为近似为球体,基于GeoHash 将二维的经纬度转换成字符串,来实现位置的划分跟指定距离的查询。特点一般使用在跟位置有关的应用。
HyperLogLog , 是一种 概率 数据结构,它使用概率算法来统计集合的近似基数 , 错误率大概在0.81%。 当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小,适合使用做 UV 统计。
BitMap ,用一个比特位来映射某个元素的状态, 只有 0 和 1 两种状态,非常典型的二值状态,且其本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型 ,优势大量节省内存空间,可是使用在二值统计场景。
在理解上述知识后,我们接下来讨论一下根据哪些策略选择相对应的应用场景下的 Redis 数据类型?
选择合适的 Redis 数据类型策略
在实际开发应用中,Redis可以适用于众多的业务场景,但我们需要怎么选择数据类型存储呢?
主要依据就是时间/空间复杂度,在实际的开发中可以考虑以下几个点:
数据量,数据本身大小 集合类型统计模式 支持单点查询/范围查询 特殊使用场景 数据量,数据本身大小当数据量比较大,数据本身比较小,使用 String 就会使用额外的空间大大增加,因为使用哈希表保存键值对,使用 dictEntry 结构保存,会导致保存每个键值对时额外保存 dictEntry 的三个指针的开销,这样就会导致数据本身小于额外空间开销,最终会导致存储空间数据大小远大于原本数据存储大小。
可以使用基于 整数数组 和 压缩列表 实现了 List 、 Hash 和 Sorted Set ,因为 整数数组 和 压缩列表 在内存中都是分配一块地址连续的空间,然后把集合中的元素一个接一个地放在这块空间内,非常紧凑,不用再通过额外的指针把元素串接起来,这就避免了额外指针带来的空间开销。而且采用集合类型时,一个 key 就对应一个集合的数据,能保存的数据多了很多,但也只用了一个 dictEntry ,这样就节省了内存。
集合类型统计模式Redis 集合类型统计模式常见的有:
聚合统计( 交集、差集、并集统计 ): 对多个集合进行聚合计算时,可以选择 Set ; 排序统计(要求集合类型能对元素保序): Redis 中 List 和 Sorted Set 是有序集合, List 是按照元素进入 List 的顺序进行排序的, Sorted Set 可以根据元素的权重来排序; 二值状态统计( 集合元素的取值就只有 0 和 1 两种 ): Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型 , Bitmap通过 BITOP 按位 与、或、异或的操作后使用 BITCOUNT 统计 1 的个数。 基数统计( 统计一个集合中不重复的元素的个数 ): HyperLogLog 是一种用于统计基数的数据集合类型 ,统计结果是有一定误差的,标准误算率是 0.81% 。需要精确统计结果的话,用 Set 或 Hash 类型。
Set 类型,适用统计用户/好友/关注/粉丝/感兴趣的人集合聚合操作,比如
统计手机APP每天的新增用户数 两个用户的共同好友Redis 中 List 和 Sorted Set 是有序集合,使用应对集合元素排序需求 ,比如
最新评论列表 排行榜Bitmap 二值状态统计,适用数据量大,且可以使用二值状态表示的统计,比如:
签到打卡,当天用户签到数 用户周活跃 用户在线状态HyperLogLog 是一种用于统计基数的数据集合类型, 统计一个集合中不重复的元素个数 ,比如
统计网页的 UV , 一个用户一天内的多次访问只能算作一次 支持单点查询/范围查询Redis 中 List 和 Sorted Set 是有序集合支持范围查询,但是 Hash 是不支持范围查询的
特殊使用场景消息队列 ,使用 Redis 作为消息队列的实现,要消息的基本要求 消息保序 、 处理重复的消息 和 保证消息可靠性 ,方案有如下:
基于 List 的消息队列解决方案 基于 Streams 的消息队列解决方案基于List 基于Strems 消息保序 使用 LPUSH/RPOP 使用 XADD/XREAD 阻塞读取 使用 BRPOP 使用 XREAD block 重复消息处理 生产者自行实现全局唯一ID Streams自动生成全局唯一ID 消息可靠性 使用 BRPOPLPUSH 使用 PENDING List自动留存消息 适用场景 消息总量小 消息总量大,需要消费组形式读取数据
基于位置 LBS 服务 ,使用 Redis 的特定 GEO 数据类型实现, GEO 可以记录经纬度形式的地理位置信息,被广泛地应用在 LBS 服务中。 比如:打车软件是怎么基于位置提供服务的。
总结
Redis 之所以那么快,是因为其基于内存的数据操作和使用 Hash 哈希表作为索引,其效率高,速度快,而且得益于其底层数据多样化使得其可以适用于众多场景,不同场景中选择合适的数据类型可以提升其查询性能。
更多编程相关知识,请访问:编程视频!!
以上就是详解Redis中的数据结构的详细内容!