面试:Redis为什么这么快呢?查询为何会变慢?
mhr18 2024-11-21 18:08 22 浏览 0 评论
原文链接:https://mp.weixin.qq.com/s/yOOqiRf-l3O4qq4A3QpI_w
越努力,越幸运
本文已收藏在GitHub中JavaCommunity, 里面有面试分享、源码分析系列文章,欢迎收藏,点赞
https://github.com/Ccww-lx/JavaCommunity
在实际开发,Redis使用会频繁,那么在使用过程中我们该如何正确抉择数据类型呢?哪些场景下适用哪些数据类型。而且在面试中也很常会被面试官问到Redis数据结构方面的问题:
- Redis为什么快呢?
- 为什么查询操作会变慢?
- Redis Hash rehash过程
- 为什么使用哈希表作为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;
…..
}
redisObject除了记录实际数据,还需要额外的内存空间记录数据长度、空间使用等元数据信息,其中包含了 8 字节的元数据和一个 8 字节指针,指针指向具体数据类型的实际数据所在位置:
其中,指针指向的就是基于Redis的底层数据结构存储数据的位置,Redis的底层数据结构:SDS,双向链表、跳表,哈希表,压缩列表、整数集合实现的。
那么Redis底层数据结构是怎么实现的呢?
Redis底层数据结构实现
我们先来看看Redis比较简单的SDS,双向链表,整数集合。
SDS、双向链表和整数集合
SDS,使用len字段记录已使用的字节数,将获取字符串长度复杂度降低为O(1),而且SDS是惰性释放空间的,你free了空间,系统把数据记录下来下次想用时候可直接使用。不用新申请空间。
整数集合,在内存中分配一块地址连续的空间,数据元素会挨着存放,不需要额外指针带来空间开销,其特点为内存紧凑节省内存空间,查询复杂度为O(1)效率高,其他操作复杂度为O(N);
双向链表, 在内存上可以为非连续、非顺序空间,通过额外的指针开销前驱/后驱指针串联元素之间的顺序。
其特点为节插入/更新数据复杂度为O(1)效率高,查询复杂度为O(N);
Hash哈希表
哈希表,其实类似是一个数组,数组的每个元素称为一个哈希桶,每个哈希桶中保存了键值对数据,且哈希桶中的元素使用dictEntry结构,
因此,哈希桶元素保存的并不是键值对值本身,而是指向具体值的指针,所以在保存每个键值对的时候会额外空间开销,至少有增加24个字节,特别是Value为String的键值对,每一个键值对就需要额外开销24个字节空间。当保存数据小,额外开销比数据还大时,这时为了节省空间,考虑换数据结构。
那来看看全局哈希表全图:
虽然哈希表操作很快,但Redis数据变大后,就会出现一个潜在的风险:哈希表的冲突问题和 rehash开销问题,这可以解释为什么哈希表操作变慢了?
当往哈希表中写入更多数据时,哈希冲突是不可避免的问题 , Redis 解决哈希冲突的方式,就是链式哈希,同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接,如图所示:
当哈希冲突也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低。
为了解决哈希冲突带了的链过长的问题,进行rehash操作,增加现有的哈希桶数量,分散单桶元素数量。那么rehash过程怎么样执行的呢?
Rehash
为了使rehash 操作更高效,使用两个全局哈希表:哈希表 1 和哈希表 2,具体如下:
- 将哈希表 2 分配更大的空间,
- 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
- 释放哈希表 1 的空间
但由于表1和表2在重新映射复制时数据大,如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。
为了避免这个问题,保证Redis能正常处理客户端请求,Redis采用了渐进式rehash。
每处理一个请求时,从哈希表 1 中依次将索引位置上的所有 entries 拷贝到哈希表 2 中,把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。
在理解完Hash哈希表相关知识点后,看看不常见的压缩列表和跳表。
压缩列表与跳表
压缩列表,在数组基础上,在压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
优点:内存紧凑节省内存空间,内存中分配一块地址连续的空间,数据元素会挨着存放,不需要额外指针带来空间开销;查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。
跳表 ,在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位,如下图所示:
比如查询33
特点:当数据量很大时,跳表的查找复杂度为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实现,适用于简单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 的消息队列解决方案
基于位置 LBS 服务,使用Redis的特定GEO数据类型实现,GEO 可以记录经纬度形式的地理位置信息,被广泛地应用在 LBS 服务中。 比如:打车软件是怎么基于位置提供服务的。
总结
Redis之所以那么快,是因为其基于内存的数据操作和使用Hash哈希表作为索引,其效率高,速度快,而且得益于其底层数据多样化使得其可以适用于众多场景,不同场景中选择合适的数据类型可以提升其查询性能。
- 上一篇:为什么Redis快
- 下一篇:浅谈Redis异步机制:如何避免单线程模型的阻塞?
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)