Redis高频面试题汇总(2024最新版)

高并发:高并发通常指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second)和并发用户数等,Redis虽然它是一个单进程单线程模型,但是Redis确实高并发业务场景下的一把利器,目前Redis的QPS已经能达到10万甚至是100万级别了,这是绝对的高并发。

高可用:Redis高可用主要体现在主从复制、sentinel(哨兵模式)和Cluster(集群模式)三者 ​

1634183247927.jpg

这个我的理解大致是这样的面试官!!

Redis的单线程指的是执行命令操作使用单线程,Redis6.x发布之后使用多线程处理网络数据的读写和协议解析,Redis单线程这么快的原因主要有这些点:

  • 采用I/O多路复用非阻塞模型处理客户端socket连接,能够极大的优化Redis服务端响应速度和效率,多路 I/O 复用技术可以让单个线程高效的处理多个连接请求,尽量减少网络 IO 的时间消耗。

  • 基于内存存储,减少磁盘IO,读取速度快

  • Redis对内部数据结构做了极致的优化,使Redis的数据结构非常高效。并且在不同的数据量和存储内容采取不同的编码方式,这就涉及到了 Redis 底层的编码转换。比如list、hash、zset 三个键使用到了 ziplist 压缩列表编码,ziplist 是一种结构紧凑的数据结构,当某一键值中所包含的元素较少时,会优先存储在 ziplist 中,当元素个数超过某一值后,才将 ziplist 转化为标准存储结构,当然这一值是在redis.conf中可自定义配置。此外SDS的内存预分配、Hash结果Rehash中的渐进式hash、ZSet顺序基于SkipList存储,都优化了数据结构,使其更快。

  • Redis在执行命令操作使用单线程,无需考虑并发锁的设计,以及多线程带来的CPU上下文切换消耗,因此执行命令更快

1634195371175.jpg

这个我的理解大致是这样的面试官!!

Redis有5种基本数据类型它们分别是String、List、Hash、Set、ZSet;此外还有三种特殊数据类型Bitmaps、Geospatial、HyperLogLog

| 数据类型 | 简单描述 | 使用场景 |

| — | — | — |

| String | string(字符串)是Redis最简单也是使用最广泛的数据结构,它的内部是一个字符数组。String(字符串)是动态字符串,允许修改;它在结构上的实现类似于Java中的ArrayList(默认构造一个大小为10的初始数组),这是冗余分配内存的思想,也称为预分配;这种思想可以减少扩容带来的性能消耗。当string(字符串)的大小达到扩容阈值时,将会对string(字符串)进行扩容,string(字符串)的扩容主要有三种情况:1.长度小于1MB,扩容后为原先的两倍; length = length * 2 2.长度大于1MB,扩容后增加1MB; length = length + 1MB 3. 字符串的长度最大值为 512MB | 缓存、计数器、分布式锁等。 |

| List | Redis的列表相当于Java语言中的LinkedList,它是一个双向链表数据结构(但是这个结构设计比较巧妙,后面会介绍),支持前后顺序遍历。链表结构插入和删除操作快,时间复杂度O(1),查询慢,时间复杂度O(n)。Redis的list(列表)不是一个简单。LinkedList,而是quicklist ——“快速列表”,quicklist是多个ziplist(压缩列表)组成的双向列表; | 链表、异步队列、微博关注人时间轴列表…… |

| Hash | Redis的hash(字典)相当于Java语言中的HashMap,它是根据散列值分布的无序字典,内部的元素是通过键值对的方式存储。hash(字典)的实现与Java中的HashMap(JDK1.7)的结构也是一致的,它的数据结构也是数组+链表组成的二维结构,节点元素散列在数组上,如果发生hash碰撞则使用链表串联在数组节点上。Redis中的hash(字典)存储的value只能是字符串值,此外扩容与Java中的HashMap也不同。Java中的HashMap在扩容的时候是一次性完成的,而Redis考虑到其核心存取是单线程的性能问题,为了追求高性能,因而采取了渐进式rehash策略。渐进式rehash指的是并非一次性完成,它是多次完成的,因此需要保留旧的hash结构,所以Redis中的hash(字典)会存在新旧两个hash结构,在rehash结束后也就是旧hash的值全部搬迁到新hash之后,新的hash在功能上才会完全替代以前的hash。 | 用户信息、Hash 表…… |

| Set | Redis的set(集合)相当于Java语言里的HashSet,它内部的键值对是无序的、唯一的。它的内部实现了一个所有value为null的特殊字典。集合中的最后一个元素被移除之后,数据结构被自动删除,内存被回收。 | 去重功能、赞、踩、共同好友…… |

| ZSet | zset(有序集合)是Redis中最常问的数据结构。它类似于Java语言中的SortedSet和HashMap的结合体,它一方面通过set来保证内部value值的唯一性,另一方面通过value的score(权重)来进行排序。这个排序的功能是通过Skip List(跳跃列表)来实现的。zset(有序集合)的最后一个元素value被移除后,数据结构被自动删除,内存被回收。 | 粉丝列表、学生成绩排序、访问量排行榜、点击量排行榜…… |

| Bitmaps | Bitmaps 称为位图,严格来说它不是一种数据类型。Bitmaps底层就是字符串(key-value)byte数组。我们可以使用普通的get/set直接获取和设值位图的内容,也可以通过Redis提供的位图操作getbit/setbit等将byte数组看成“位数组”来处理。Bitmaps 的“位数组”每个单元格只能存储0和1,数组的下标在Bitmaps中称为偏移量。Bitmaps设置时key不存在会自动生成一个新的字符串,如果设置的偏移量超出了现有内容的范围,就会自动将位数组进行零扩充 | 员工打卡…… |

| Geospatial | Geospatial是Redis在3.2版本以后增加的地理位置GEO模块 | 微信附近的人,在线点餐“附近的餐馆”…… |

| HyperLogLog | HyperLogLog是用来做基数统计的算法,它提供不精确的去重计数方案(这个不精确并不是非常不精确),标准误差是0.81%,对于UV这种统计来说这样的误差范围是被允许的。HyperLogLog的优点在于,输入元素的数量或者体积非常大时,基数计算的存储空间是固定的。在Redis中,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2^64个不同的基数。但是HyperLogLog只能统计基数的大小(也就是数据集的大小,集合的个数),他不能存储元素的本身,不能向set集合那样存储元素本身,也就是说无法返回元素。 | 基数统计比如UV等 |

1634199222625.jpg

这个我的理解大致是这样的面试官!!

Redis的数据结构均可以通过EXPIRE key seconds 的方式设置key的过期时间(TTL)。我们也习惯的认为Redis的key过期时间到了,就会自动删除,显然这种想法并不正确。Redis的设计考虑到性能/内存等综合因素,设计了一套过期策略。

  • 主动删除(惰性删除)

  • 被动删除(定期策略)

主动删除(惰性删除)指的是当key被访问的时候,先校验key是否过期,如果过期了则主动删除。 被动删除(定期策略)指的是Redis服务器定时随机的测试key的过期时间,如果过期了则被动删除。被动删除的存在必不可少,因为存在一些过期且永久不在访问的key,如果都依赖主动删除,那么它们将会永久占用内存。 Redis为了保证提供高性能服务,被动删除过期的key,采用了贪心策略/概率算法,默认每隔10秒扫描一次,具体策略如下:

  1. 从过期字典(设置了过期时间的key的集合)中随机选择20个key,检查其是否过期

  2. 删除其中已经过期的key

  3. 如果删除的过期key数量大于25%,则重复步骤1

此外开发在设计Redis缓存架构时,一定要注意要尽可能的避免(禁止)将大量的key设置为同一过期时间,因为结合被动删除可知,Redis被动删除过期key时,会导致服务短暂的不可用;如果存在大量key同时过期,这会导致被动删除key的三个步骤循环多次,从而导致Redis服务出现卡顿情况,这种情况在大型流量项目是无法接收的。 因此为了避免这种情况出现,一定要将一些允许过期时间不需要非常精确的key,设置较为随机的过期时间,这样就可以将卡顿时间缩小。 ​

1634200126269.jpg

这个我的理解大致是这样的面试官!!

在分布式场景中我们常见的分布式锁解决方案有(如果自己都会可以把其他两种也在这带出来,如果不会那就别把自己坑了呀!):

  1. 基于数据库锁机制实现的分布式锁

  2. 基于Zookeeper实现的分布式锁

  3. 基于Redis实现的分布式锁

而关于Redis实现分布式锁的方案是这样的。 如果Redis是在单机环境中我们可以通过,Redis提供的原子指令来实现分布式锁

set key value [EX seconds] [PX milliseconds] [NX|XX]

为了防止A加的锁,被B删除了,可以加锁时传入客户端加锁标记,只有当客户端传入的标记和锁标记相同时才允许解锁,不过Redis并未提供这样的功能,我们只能通过Lua脚本来处理,因为Lua脚本可以保证多个指令的原子性执行。最后我们还要考虑锁的超时问题,如果客户端一直不释放锁肯定也是不行的,因此锁只能保证在指定的超时时间范围内不被其他客户端解锁,超时之后就自动释放了,这种情况很难我们可以这样优化:

  1. 尽可能不要在Redis分布式锁中执行较长的任务,尽可能的缩小锁区间内执行代码,就像单JVM锁中的synchronized优化一样,我们可以考虑优化锁的区间

  2. 多做压力测试和线上真实场景的模拟测试,估算一个合适的锁超时时间

  3. 做好Redis分布式锁超时任务未执行完的问题发生后,数据恢复手段的准备

如果是在分布式环境中,会增加一个新的问题,比如sentinel+一主多从环境中,可能存在客户端在主节点上申请了锁,但是同步未完成,主节点宕机了,此时新选举的主节点上锁是失效的。 对于这种情况的处理应该是这么考虑的,首先Redis主从同步直接无论如何都无法解决数据会有丢失的情况。所以我们考虑把像一个Redis申请锁,变成像多个单机Redis申请锁,只有大部分申请成功就行。这种思想就是RedLock(红锁)。 RedLock通过使用多个Redis实例,各个实例之间没有主从关系,相互独立;加锁的时候,客户端向所有的节点发送加锁指令,如果过半的节点set成功,就加锁成功。释放锁时,需要向所有的节点发送del指令来释放锁。 红锁虽然解决了主从同步的问题,但是带来新的复杂问题:

  • 第一个问题是时钟漂移

  • 第二个问题是客户端像不同的Redis服务端申请锁成功的时间是不同的

因此在RedLock中需要计算申请的锁的最小有效时长。假设客户端申请锁成功,第一个key设置成功的时间为TF,最后一个key设置成功的时间为TL,锁的超时时间为TTL,不同进程之间的时钟差异为CLOCK_DIFF,则锁的最小有效时长是:

TIME = TTL - (TF- TL) - CLOCK_DIFF

采用Redis来实现分布式锁,离不开服务器宕机等不可用问题,这里RedLock红锁也一样,即使是多台服务器申请锁,我们也要考虑服务器宕机后的处理,官方建议采用AOF持久化处理。 但是AOF持久化只对正常SHUTDOWN这种指令能做到重启恢复,但是如果是断电的情况,可能导致最后一次持久化到断电期间的锁数据丢失,当服务器重启后,可能会出现分布式锁语义错误的情况。所以为了规避这种情况,官方建议Redis服务重启后,一个最大客户端TTL时间内该Redis服务不可用(不提供申请锁的服务),这确实可以解决问题,但是显而易见这肯定影响Redis服务器的性能,并且在多数节点都出现这种情况的时候,系统将出现全局不可用的状态。 ​

1634202927569.jpg

这个我的理解大致是这样的面试官!!

Redis的非常快,很大一部分原因是因为Redis的数据存储在内存中,既然在内存中,那么当服务器宕机或者断电的时候,数据就会全部丢失了,所以Redis提供了两种机制来保证Redis数据不会因为故障而全部丢失,这种机制称为Redis的持久化机制。 Redis的持久化机制有两种:

  • RDB(Redis Data Base) 内存快照

  • AOF(Append Only File) 增量日志

RDB(Redis DataBase) 指的是在指定的时间间隔内将内存中的数据集快照写入磁盘,RDB是内存快照(内存数据的二进制序列化形式)的方式持久化,每次都是从Redis中生成一个快照进行数据的全量备份。 优点:

  • 存储紧凑,节省内存空间

  • 恢复速度非常快

  • 适合全量备份、全量复制的场景,经常用于灾难恢复(对数据的完整性和一致性要求相对较低的场合)

缺点:

  • 容易丢失数据,容易丢失两次快照之间Redis服务器中变化的数据。

  • RDB通过fork子进程对内存快照进行全量备份,是一个重量级操作,频繁执行成本高。

  • fork子进程,虽然共享内存,但是如果备份时内存被修改,最大可能膨胀到2倍大小。

RDB触发的规则分为两大类,分别是手动触发和自动触发: 自动触发:

  1. 配置触发规则

  2. shutdown触发

  3. flushall触发

手动触发:

  1. save

  2. bgsave

**AOF(Append Only File)**是把所有对内存进行修改的指令(写操作)以独立日志文件的方式进行记录,重启时通过执行AOF文件中的Redis命令来恢复数据。AOF能够解决数据持久化实时性问题,是现在Redis持久化机制中主流的持久化方案(后续会谈到4.0以后的混合持久化)。 优点:

  • 数据的备份更加完整,丢失数据的概率更低,适合对数据完整性要求高的场景

  • 日志文件可读,AOF可操作性更强,可通过操作日志文件进行修复

缺点:

  • AOF日志记录在长期运行中逐渐庞大,恢复起来非常耗时,需要定期对AOF日志进行瘦身处理(后续详述)

  • 恢复备份速度比较慢

  • 同步写操作频繁会带来性能压力

AOF日志是以文件的形式存在的,当程序对AOF日志文件进行写操作时,实际上将内容写到了内核为文件描述符分配的一个内存缓冲区中,随后内核会异步的将缓冲区中的数据刷新到磁盘中。如果缓冲区中的数据没来得及刷回磁盘时,服务器宕机了,这些数据就会丢失。 因此Redis通过调用Linux操作系统的glibc提供的fsync(int fid)来将指定文件的内容强制从内核缓冲区刷回磁盘,以此来保证缓冲区中的数据不会丢失。不过这是一个IO操作,相比Redis的性能来说它是非常慢的,所以不能频繁的执行。 Redis配置文件中有三种刷新缓冲区的配置:

appendfsync always 每次Redis写操作,都写入AOF日志,这种配置理论上Linux操作系统扛不住,因为Redis的并发远远超过了Linux操作系统提供的最大刷新频率,就算Redis写操作比较少的情况,这种配置也是非常耗性能的,因为涉及到IO操作,所以这个配置基本上不会用 ​

appendfsync everysec 每秒刷新一次缓冲区中的数据到AOF文件,这个Redis配置文件中默认的策略,兼容了性能和数据完整性的折中方案,这种配置,理论上丢失的数据在一秒钟左右 ​

appendfsync no Redis进程不会主动的去刷新缓冲区中的数据到AOF文件中,而是直接交给操作系统去判断,这种操作也是不推荐的,丢失数据的可能性非常大。 ​

前面提到AOF的缺点时,说过AOF属于日志追加的形式来存储Redis的写指令,这会导致大量冗余的指令存储,从而使得AOF日志文件非常庞大,这种情况不仅占内存,也会导致恢复的时候非常缓慢,因此Redis提供重写机制来解决这个问题。Redis的AOF持久化机制执行重写后,保存的只是恢复数据的最小指令集,我们如果想手动触发可以使用如下指令

bgrewriteaof

Redis4.0后的重写使用的是RDB快照和AOF指令拼接的方式,在AOF文件的头部是RDB快照的二进制形式的数据,尾部是快照产生后发生的写入操作的指令。 由于重写AOF文件时,会对Redis的性能带来一定的影响,因此也不能随便的进行自动重写,Redis提供两个配置用于自动进行AOF重写的指标,只有这两个指标同时满足的时候才会发生重写:

auto-aof-rewrite-percentage 100:指的是当文件的内存达到原先内存的两倍

auto-aof-rewrite-min-size 64mb:指的是文件重写的最小内存大小

此外Redis4.0后大部分的使用场景都不会单独使用RDB或者AOF来做持久化机制,而是兼顾二者的优势混合使用。 最后来总结这两者,到底用哪个更好呢?

  • 推荐是两者均开启

  • 如果对数据不敏感,可以选单独用RDB

  • 不建议单独用AOF,因为可能会出现Bug

最后

我想问下大家当初选择做程序员的初衷是什么?有思考过这个问题吗?高薪?热爱?

既然入了这行就应该知道,这个行业是靠本事吃饭的,你想要拿高薪没有问题,请好好磨练自己的技术,不要抱怨。有的人通过培训可以让自己成长,有些人可以通过自律强大的自学能力成长,如果你两者都不占,还怎么拿高薪?

架构师是很多程序员的职业目标,一个好的架构师是不愁所谓的35岁高龄门槛的,到了那个时候,照样大把的企业挖他。为什么很多人想进阿里巴巴,无非不是福利待遇好以及优质的人脉资源,这对个人职业发展是有非常大帮助的。

如果你也想成为一名好的架构师,那或许这份Java核心架构笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

中高级开发必知必会:

现Bug

最后

我想问下大家当初选择做程序员的初衷是什么?有思考过这个问题吗?高薪?热爱?

既然入了这行就应该知道,这个行业是靠本事吃饭的,你想要拿高薪没有问题,请好好磨练自己的技术,不要抱怨。有的人通过培训可以让自己成长,有些人可以通过自律强大的自学能力成长,如果你两者都不占,还怎么拿高薪?

架构师是很多程序员的职业目标,一个好的架构师是不愁所谓的35岁高龄门槛的,到了那个时候,照样大把的企业挖他。为什么很多人想进阿里巴巴,无非不是福利待遇好以及优质的人脉资源,这对个人职业发展是有非常大帮助的。

如果你也想成为一名好的架构师,那或许这份Java核心架构笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

中高级开发必知必会:

[外链图片转存中…(img-1D5i7jZb-1714442440865)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值