当前位置: 首页 > news >正文

Redis经典面试题

Redis经典面试题

问题1: Redis为什么这么快?

1.1 基于内存实现

Redis的数据都是存放在内存中,而像关系型数据库Mysql的数据存放在磁盘。访问磁盘数据是要进行网络IO连接,是很耗时的,而内存的数据访问和操作是相当快的。

1.2 高效的数据结构

我们都知道,mysql为了提高效率,采用了B+树的数据结构,对于一个应用场景来说合理的数据结构能够性能更好。我们来看看Redis的数据结构-内部编码。

String :动态字符串SDS。
List :双端链表LinkedList + 压缩链表zipList。
Hash:压缩链表zipList + 字典hashTable。
Set:字典hashTable。
ZSet:压缩链表zipList + 跳跃表skipList。

SDS简单动态字符串

image

  • 字符串长度处理:Redis获取字符串长度的复杂度 O(1),在C语言获取字符串长度从头开始遍历,复杂度为O(N).
  • 空间预分配:字符串频繁的修改,内存分配就越频繁,就会消耗性能,而SDS修改和空间扩展,会额外分配未使用的空间,减少性能消耗。
  • 惰性空间释放: SDS缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free记录的空间,减少内存的肥分配。
  • 二进制安全:Redis可以记录二进制的数据,在C语言中字符串遇到’\0’会结束,而SDS中标志字符串结束的是len属性。

字典
Redis作为K-V型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key就可以找到对应的value。哈希表的特性就是,在O(1)时间复杂度下就可以获得对应的值。

跳跃表
image

  • 跳跃表就是在链表的基础上,增加多级索引来提升查询效率
  • 跳跃表支持平均O(logN) 到 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。

1.3 合适的数据编码

Redis支持多种数据机构,每种基本类型,对应多种数据结构。什么时候使用什么样的数据结构,使用什么样的编码,是redis设计者总结优化的结果。

1.4 合理的线程模型

I/O多路复用

I/O多路复用技术可以让单个线程高效的处理多个连接请求,而Redis将epoll作为IO多路复用的技术实现。并且,Redis的自身事件处理模型将epoll的连接,读写,关闭都转化为事件,不在网络I/O上浪费过多时间。

I/O:网络IO
多路:多个网络连接
复用:复用同一个线程
I/O多路复用,就是一个同步IO模型,它实现了一个线程可以监听多个文件句柄;一旦文件句柄就绪,就会通知应用程序进行相对于的读写操作,当没有文件句柄就绪时,就会阻塞应用程序,交出CPU。

单线程

Redis是使用单线程来进行指令操作的,避免了CPU不必要的上下文切换和锁的竞争的损耗,并且是基于内存操作数据,以达到最快的速度。

Redis6.0版本引入了多线程的概念,但一般是网络相关的指令等IO操作时,会新开一个线程去执行,而执行命令操作数据等指令还是单线程去串行执行,仍是线程安全的。

这样做的目的是因为redis的性能瓶颈在于网络IO而不是CPU,使用多线程提升IO读写的效率,从而提高整个Redis的性能。

1.5 虚拟内存机制

Redis直接自己构建了VM机制,不会像一般的系统会调用系统函数处理,会浪费一定的时间去移动和请求。

Redis的虚拟内存机制是什么?

虚拟内存机制就是暂时把不经常使用的数据(冷数据)从内存交换到磁盘,从而腾出宝贵的内存空间以供其他需要访问的数据(热数据)。通过VM机制可以实现冷热数据分离,使热数据在内存中,冷数据保存在磁盘中。这样就可以避免因内存不足而造成访问速度下降的问题。

问题二:Mysql和Redis如何保证读写一致

读取缓存步骤一般没有什么问题,但是一旦遇到数据的更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(Mysql)间的数据一致性问题了。

不管是先更新数据库DB,再删除Redis缓存;还是先删除Redis缓存,再更新数据库DB,都有可能出现数据不一致的情况。

举个栗子:
1,如果删除了Redis缓存,还没有来得及更新数据库DB,另一个线程来读取,发现缓存为空,则去读取数据库的旧值,并写入缓存,此时缓存中为脏数据。
2,如果先更新了数据库DB,在删除缓存前,出现了异常,导致没有删除掉缓存,则也会出现数据不一致。

因为读和写是并发的,没法保证顺序性,就会出现数据库和缓存数据不一致的情况。

目前有三个解决方案供参考。

  • 缓存延时双删
  • 删除缓存重试机制
  • 读取binlog异步删除缓存

缓存延时双删:

延时双删流程:
1,写请求;
2,删除缓存;
3,更新DB;
4,休眠一会,删除缓存;

这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒,这样的目的是确保读请求结束,写请求可以删除读请求产生的脏数据。

这个方案还可以,但是如果第二次删除缓存失败了呢?缓存和数据库的数据还是不一致。那么是否可以加上过期时间呢?业务接受在这过期时间内的数据不一致吗?还是有其他更好的方案呢?

删除缓存重试机制

image

删除缓存重试机制流程:
1,写请求更新数据库;
2,某种原因删除缓存key失败
3,将删除缓存失败的key丢到消息队列;
4,消费消息队列的key,获取要删除的key;
5,重试删除缓存操作;

这个方案还不错,最终能够保证缓存和数据库的一致性。然而有一个缺点,会对业务代码造成大量入侵。

读取binlog异步删除缓存

image

binlog是所有数据库表结构变更,以及表数据修改的二进制日志文件。
binlog有两个常用的使用场景:

  • 主从复制:mysql replication在master端开启binlog,master把它的二进制文件传递给slaves来达到master-slave数据一致性的目的。
  • 数据恢复:通过mysqlbinlog工具来恢复数据。

以mysql为例:
1,可以使用阿里的canal将binlog日志采集发送到MQ消息队列;
2,然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性。
大啊是的大啊是的

问题三:Redis的Hash冲突了怎么办

Redis作为一个K-V型的内存数据库,它使用用一张全局的哈希表来保存所有的键值对。这张哈希表,由很多个哈希桶组成,每个哈希桶中的entry元素保存了key和value指针,其中*key指向了实际的健, *value指向了实际的值。
image

哈希表的查找效率很快,有点类似于java中的HashMap,它能够在O(1)时间复杂度快速找到键值对。首先通过key计算哈希值,通过哈希值找到哈希桶的位置,定位到entry,从entry中找到对应的数据。

什么是哈希冲突?

哈希冲突:通过不同的key,计算出的哈希值一样,导致落到同一个哈希桶中。

Redis为了解决哈希冲突,采用了链式哈希。链式哈希就是同一个哈希桶中的多个元素,用链表来保存,它们之间依次用指针来连接。

可能会有人有疑问,哈希冲突链上的元素只能通过指针逐一查找,当往哈希表插入更多的数据,那么发生哈希冲突的可能性也就越高,冲突链表也就越长,那查询效率不就是很低?

为了保持高效,Redis会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis默认使用两张全局哈希表,一个用于当前使用,称为主哈希表,另一个用作扩容使用,称为备用哈希表。

值得注意的是,rehash的过程不是一次性的,这样会造成redis阻塞,无法提供服务,而是采用了渐进式rehash。

问题四:在生成RDB期间,Redis可以同时处理读写请求吗

可以的,Redis提供两个指令生成RDB文件,分别是save和bgsave。

  • 如果是save,会阻塞,因为是主线程执行的。
  • 如果是bgsave,会fork出一个子进程来生成RDB文件,主线程可以继续处理客户端的请求。

问题五:什么是布隆过滤器

定义:

布隆过滤器是一种占用空间很小的数据结构,它由一个很长的二进制向量和一组Hash映射函数组成,它用于检索一个元素是否在一个集合中,空间效率和查询时间比一般的算法要好得多,缺点是有一定的误识别率和删除困难。

原理:

布隆过滤器的原理是:假设我们有集合A,A中有n个元素。利用k个哈希散列函数,将A中的每个元素映射到一个长度为a位的数组B中的不同位置上,这些被映射的位置上的二进制数均设置为1。如果待检查的元素,经过这k个哈希散列函数的映射后,发现其k个位置上的二进制数全部为1,这个元素很可能属于A,反之,一定不属于集合A。
(不理解的反复读5遍)

举个栗子:

假设集合A有3个元素,{d1,d2,d3}。有一个哈希函数为Hash1。现在将集合A的每个元素都映射到一个长度为16的数组B。这个数组只存0和1,默认为0.
现在我们将d1映射过来,假设Hash1(d1)= 2,我们就把数组B中,下标为2的格子改为1。
现在将d2也映射过来,假设Hash1(d2)= 5,我们就把数组B中,下标为5的格子改为1。
现在将d3也映射过来,假设hash1(d3)= 2,我们就把数组B中,下标为2的格子改为1。
目前Hash1(d1)和Hash(d3)都等于2。

因此,我们要确认一个元素dn是否在集合A中,只要算出Hash1(dn)得到的索引下标,只要是0,那么元素dn肯定不在集合A中。如果索引下标是1呢?那该元素可能是集合A的某个元素。因为,d1和d3得到的下标值都是1,还有可能是其他元素映射的,布隆过滤器是存在这个缺点的:会存在哈希碰撞的假阳性,判断存在误差。

如何减少误差:

  • 多几个哈希函数映射,降低哈希碰撞的概率。
  • 增加数组的长度,可以增大哈希函数生成的数据范围,也能降低哈希碰撞的概率。

即使存在误差,我们发现,布隆过滤器没有存储完整的数据,而是通过一系列的哈希函数找出位置,并填充二进制向量。如果数据量很大,布隆过滤器通过极少的错误率,换取了存储空间的极大节省,还是较为划算的。

实现布隆过滤器的开源类库:

目前布隆过滤器已经有实现的类库了,比如Google的Guava类库,Twitter的Algebird类库,或者基于Redis自带的Bitmaps自行实现设计也是可以的。

问题六:Redis的过期key的删除策略

Reids中,我们可以设置缓存key的过期时间,Redis的过期策略是指,当Reids中缓存的key过期了,Redis如何处理。
过期策略有以下三种:

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期时间的数据,对内存友好;但是会占用大量的CPU的资源来处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才去判断该key是否过期,过期则清除。该策略最大化的节省了CPU资源,却对内存非常不友好。极端情况下会出现,大量的过期key没有被访问过,从而这些key不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会去扫描数据库中expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案,通过调整定期扫描的时间间隔和每次扫描的限定数量,可以在不同情况下使用CPU和内存资源达到最优的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向健空间的某个健的指针,value是该健的毫秒精度的UNIX时间戳表示的过期时间。)

Redis同时使用了惰性过期和定期过期两种过期策略。

问题七:Redis的内存淘汰机制

Redis的内存淘汰机制和过期ky的删除策略不要搞混淆了。内存淘汰机制是当Redis的内存空间不够了,设置某一种策略来处理数据。

Redis4.0之后内存淘汰机制一共有8种。

  • noeviction:不会淘汰任何数据,当使用空间超过maxmemory时,新增的数据会直接报错,Redis默认的淘汰机制。
  • volatile-ttl: 优先淘汰过期时间快到的键值。
  • volatile-random: 从设置了过期时间的健中随机淘汰。
  • volatile-lru: 从设置了过期时间的健中使用LRU算法,最近最少使用的健淘汰。
  • volatile-lfu: 从设置来过期时间的健中使用LFU算法,最近最少使用的健淘汰。
  • allkeys-random: 从所有的健中随机淘汰。
  • allkeys-lru: 从所有的健中使用LRU算法,最近最少使用的健淘汰。
  • allkeys-lfu: 从所有的健中使用LFU算法,最近最少使用的健淘汰。

根据系统的不同业务场景,选择合适的淘汰机制。

关于Redis实现的LRU和LFU算法的详细内容可以查阅这篇文章。
参考文档:https://wenku.baidu.com/view/85b703173a68011ca300a6c30c2259010202f3fa.html

问题八:怎么实现Redis的高可用

在实际项目中使用Redis,肯定不会单点部署Redis服务的,因为单点部署一旦宕机,就不可用了。为了实现高可用,通常是部署多台Redis。
Redis主要有三种部署模式:

  • 主从模式
  • 哨兵模式
  • 集群(Cluster)模式

8.1 主从模式

主从模式中,Redis部署来多台机器,由主节点master,负责读写操作,由从节点slave,只负责读操作。从节点的数据来自主节点,实现的原理就是主从复制机制。

主从复制包括全量复制增量复制和两种。

一般当slave节点第一次链接master节点,会采用全量复制。
slave和master全量复制后,当master上的数据发生变更时,采用增量复制。

8.2 哨兵模式

主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点切换为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8版本开始正式提供了哨兵模式来解决此问题。

哨兵模式,由一个或多个Sentinel组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态后,自动将下线的主节点所属的从节点升级为新的主节点。但是一个哨兵进程对Redis节点监视,就可能出现单点问题,因此,一般使用多个哨兵来监控Redis节点,并且各个哨兵之间还会进行监控。

哨兵模式的主要功能有:

  • 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
  • 自动故障转移(Automatic Failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
  • 配置提供者(Configuration Provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
  • 通知(Notification):哨兵可以将故障转移的结果发送给客户端。

8.3 集群(Cluster)模式

哨兵模式基于主从复制模式,实现读写分离,而且可以故障自动转移,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此Cluster集群模式应用而是,它是在Redis3.0推出的,实现了分布式存储。对数据进行分片,也就是每台Redis实例存储了不同的数据,来解决在线扩容的问题,并且也提供了主从复制和故障转移的功能。但分布式存储也带了其他但问题,在此不展开描述。

关于主从复制模式,哨兵模式,Cluster集群模式的更多详细内容,查看另外的单独文章。

相关文章:

  • 【开发经验】通知气泡实现思路
  • 机器学习损失函数
  • Set接口学习(2)
  • Windows下更改并使用NTP
  • Framework面试之(Binder)(Handler)脚踏大厂面试大赏
  • Redis的不同系统安装教程
  • 几种Set的比较
  • 使用 ECK 在 Kubernetes 集群中管理 Elastic Stack
  • 在Qt中使用MySQL
  • java---SPFA算法---最短路(4)(每日一道算法2022.8.30)
  • 2382. 删除操作后的最大子段和--(phase2--day3)
  • 时间复杂度计算题
  • 不愧是阿里内部“千亿级并发系统架构设计笔记”面面俱到,太全了
  • SpringCloud之配置中心
  • C++征途 --- Stack(栈)容器和Queue(队列)容器
  • 《Java编程思想》读书笔记-对象导论
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JAVA之继承和多态
  • leetcode98. Validate Binary Search Tree
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • MySQL的数据类型
  • Python 反序列化安全问题(二)
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 开源地图数据可视化库——mapnik
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 算法---两个栈实现一个队列
  • 算法之不定期更新(一)(2018-04-12)
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • mysql面试题分组并合并列
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • 第二十章:异步和文件I/O.(二十三)
  • 湖北分布式智能数据采集方法有哪些?
  • ​TypeScript都不会用,也敢说会前端?
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #define 用法
  • #include
  • (145)光线追踪距离场柔和阴影
  • (arch)linux 转换文件编码格式
  • (算法二)滑动窗口
  • (一)基于IDEA的JAVA基础12
  • (转)http协议
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • ***测试-HTTP方法
  • .CSS-hover 的解释
  • .Net IOC框架入门之一 Unity
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • @EnableConfigurationProperties注解使用
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决
  • [2010-8-30]
  • [AX]AX2012 SSRS报表Drill through action
  • [daily][archlinux][game] 几个linux下还不错的游戏
  • [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-controller-manager失败
  • [hdu2196]Computer树的直径