关于redis:2021Java后端工程师面试指南Redis

41次阅读

共计 7847 个字符,预计需要花费 20 分钟才能阅读完成。

前言

文本已收录至我的 GitHub 仓库,欢送 Star:https://github.com/bin3923282…
种一棵树最好的工夫是十年前,其次是当初

Tips

面试指南系列,很多状况下不会去深挖细节,是小六六以被面试者的角色去回顾常识的一种形式,所以我默认大部分的货色,作为面试官的你,必定是懂的。

https://www.processon.com/vie…

下面的是脑图地址

叨絮

这个系列也写了几篇了,明天咱们来看看 redis, 后面的能够去 github 上看看。
而后上面是后面的文章汇总

  • 2021-Java 后端工程师面试指南 -(引言)
  • 2021-Java 后端工程师面试指南 -(Java 根底篇)
  • 2021-Java 后端工程师面试指南 -(并发 - 多线程)
  • 2021-Java 后端工程师面试指南 -(JVM)
  • 2021-Java 后端工程师面试指南 -(MySQL)

说说什么是 redis 吧

Redis 是一个凋谢源代码(BSD 许可)的内存中数据结构存储,用作数据库,缓存和音讯代理。它反对数据结构,例如字符串,哈希,列表,汇合,带范畴查问的排序汇合,位图,超日志,带有半径查问和流的天文空间索引。Redis 具备内置的复制,Lua 脚本,LRU 逐出,事务和不同级别的磁盘持久性,并通过 Redis Sentinel 和 Redis Cluster 主动分区提供了高可用性。

说说 Redis 有哪些优缺点

长处

  • 性能优异,Redis 能读的速度是 110000 次 /s,写的速度是 81000 次 /s。
  • 数据长久化,反对 AOF 和 RDB 两种长久化形式。
  • 事务,Redis 的所有操作都是原子性的,同时 Redis 还反对对几个操作合并后的原子性执行。
  • 构造丰盛,除了反对 string 类型的 value 外还反对 hash、set、zset、list 等数据结构。
  • 主从复制,主机会主动将数据同步到从机,能够进行读写拆散。

毛病

  • 库容量受到物理内存的限度,不能用作海量数据的高性能读写,因而 Redis 适宜的场景次要局限在较小数据量的高性能操作和运算上。
  • 宕机,宕机前有局部数据未能及时同步到从机,切换 IP 后还会引入数据不统一的问题,升高了零碎的可用性。
  • redis 较难反对在线扩容,在集群容量达到下限时在线扩容会变得很简单。为防止这一问题,运维人员在零碎上线时必须确保有足够的空间,这对资源造成了很大的节约。

说说为啥要用缓存

次要是为了进步零碎的吞吐量,应答高并发,高性能场景

为什么要用 Redis 而不必 map/guava 做缓存?

  • Java 实现的 Map 是本地缓存,如果有多台实例 (机器) 的话,每个实例都须要各自保留一份缓存,缓存不具备一致性
  • Redis 实现的是分布式缓存,如果有多台实例 (机器) 的话,每个实例都共享一份缓存,缓存具备一致性。
  • Java 实现的 Map 不是业余做缓存的,JVM 内存太大容易挂掉的。个别用做于容器来存储长期数据,缓存的数据随着 JVM 销毁而完结。Map 所存储的数据结构,缓存过期机制等等是须要程序员本人手写的。
  • Redis 是业余做缓存的,能够用几十个 G 内存来做缓存。Redis 个别用作于缓存,能够将缓存数据保留在硬盘中,Redis 重启了后能够将其复原。原生提供丰盛的数据结构、缓存过期机制等等简略好用的性能。

Redis 为什么这么快

1、齐全基于内存,绝大部分申请是纯正的内存操作,十分疾速。数据存在内存中,相似于 HashMap,HashMap 的劣势就是查找和操作的工夫复杂度都是 O(1);

2、数据结构简略,对数据操作也简略,Redis 中的数据结构是专门进行设计的;

3、采纳单线程,防止了不必要的上下文切换和竞争条件,也不存在多过程或者多线程导致的切换而耗费 CPU,不必去思考各种锁的问题,不存在加锁开释锁操作,没有因为可能呈现死锁而导致的性能耗费(绝大多数的瓶颈不在 cpu)

4、应用多路 I / O 复用模型,非阻塞 IO;

5、应用底层模型不同,它们之间底层实现形式以及与客户端之间通信的利用协定不一样,Redis 间接本人构建了 VM 机制,应用了 resp 协定

聊聊 resp 协定吧

Redis 是 Redis 序列化协定,Redis 客户端 RESP 协定与 Redis 服务器通信。Redis 协定在以下几点之间做出了折衷:

  • 简略的实现
  • 疾速地被计算机解析
  • 简略得能够能被人工解析

其实就是一个二进制的序列化协定,举几个简略的例子哈
在 RESP 中,某些数据的类型取决于第一个字节:

“+”代表简略字符串 Simple Strings

“+”代表谬误类型

“:”代表整数

基于这种协定的话,其实咱们能够本人去实现一个 redis 的客户端,当前有机会给大家写写。

如果万一 CPU 成为你的 Redis 瓶颈了,或者,你就是不想让服务器其余核闲置,那怎么办?

那也很简略,你多起几个 Redis 过程就好了。Redis 是 key-value 数据库,又不是关系数据库,数据之间没有束缚。只有客户端分清哪些 key 放在哪个 Redis 过程上就能够了。redis-cluster 能够帮你做的更好。

说说 Redis 的根本数据结构

  • String 整数,浮点数或者字符串
  • Set 汇合
  • Zset 有序汇合
  • Hash 散列表
  • List 列表

那说说有序汇合的实现形式是哪种数据结构?

跳跃表。

  • 当数据较少时,sorted set 是由一个 ziplist 来实现的。
  • 当数据多的时候,sorted set 是由一个 dict + 一个 skiplist 来实现的。简略来讲,dict 用来查问数据到分数的对应关系,而 skiplist 用来依据分数查问数据(可能是范畴查找)。

说说 redis 的底层数据结构

sds

Redis 的字符串,不是 C 语言中的字符串(即以空字符’\0’结尾的字符数组),它是本人构建了一种名为 简略动静字符串(simple dynamic string,SDS)的形象类型,并将 SDS 作为 Redis 的默认字符串示意。

1、len 保留了 SDS 保留字符串的长度

2、buf[] 数组用来保留字符串的每个元素

3、free j 记录了 buf 数组中未应用的字节数量

链表

链表是一种罕用的数据结构,C 语言外部是没有内置这种数据结构的实现,所以 Redis 本人构建了链表的实现

字典

字典又称为符号表或者关联数组、或映射(map),是一种用于保留键值对的形象数据结构。字典中的每一个键 key 都是惟一的,通过 key 能够对值来进行查找或批改。C 语言中没有内置这种数据结构的实现,所以字典仍然是 Redis 本人构建的。

跳跃表
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目标。具备如下性质:

1、由很多层构造组成;

2、每一层都是一个有序的链表,排列程序为由高层到底层,都至多蕴含两个链表节点,别离是后面的 head 节点和前面的 nil 节点;

3、最底层的链表蕴含了所有的元素;

4、如果一个元素呈现在某一层的链表中,那么在该层之下的链表也全都会呈现(上一层的元素是以后层的元素的子集);

5、链表中的每个节点都蕴含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

压缩列表

压缩列表(ziplist)是 Redis 为了节俭内存而开发的,是由一系列非凡编码的间断内存块组成的程序型数据结构,一个压缩列表能够蕴含任意多个节点(entry),每个节点能够保留一个字节数组或者一个整数值。

压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据依照肯定规定编码在一块间断的内存区域,目标是节俭内存。

说说缓存雪崩

一个缓存雪崩发过程

  • redis 集群大面积故障
  • 缓存生效,但仍然大量申请拜访缓存服务 redis
  • redis 大量生效后,大量申请转向到 mysql 数据库
  • mysql 的调用量暴增,很快就扛不住了,甚至间接宕机
  • 因为大量的应用服务依赖 mysql 和 redis 的服务,这个时候很快调演变成各服务器集群的雪崩,最初网站彻底解体。

如何解决缓存雪崩

第一种计划:缓存层设计成高可用,避免缓存大面积故障。即便个别节点、个别机器、甚至是机房宕掉,仍然能够提供服务,例如 Redis Sentinel 和 Redis Cluster 都实现了高可用。

第二种计划:在批量往 Redis 存数据的时候,把每个 Key 的生效工夫都加个随机值就好了,这样能够保证数据不会在同一时间大面积生效,我置信,Redis 这点流量还是顶得住的。

那你聊聊缓存击穿

我集体了解 击穿 就是侧面刚 比方我是矛 你是盾 我间接把你的盾击穿,就是比方 几个热点 Key 同时几百万并发间接把 redis 干掉了,而后数据全部打到数据库的状况,或者是 redis 的这几个热点数据生效的情景下,同时全副的并发查这个热数据,导致最初打到数据库的状况 这个就是缓存击穿。

如何解决缓存击穿

还是分布式锁 哈哈 因为分布式锁能管制到数据库的最初一到防线
redis 做集群 哨兵

失常来说个别零碎的 qps 都有一个峰值,个别咱们应用能抗住这个峰值的内存去做这个缓存

那你说说缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户一直发动申请,如发动为 id 为“-1”的数据或 id 为特地大不存在的数据。这时的用户很可能是攻击者,攻打会导致数据库压力过大。

如何解决缓存穿透

第一种计划 和下面的双重锁一样 如果是拿到数据库为空 那么就给这个 key 设置一个 null 值 工夫设置短一点 30s,这样下次并发进来就不会说把数据打到咱们的数据库上了

还有就是咱们写代码的时候 要对一些非法的申请参数校验 我置信大家都是这样做的。

第二种计划 采纳咱们第一篇中学到的一个高级用法 bitMap,查问的时候先查 bitmap 确定是否含有这个 key

说说你是怎么解决缓存一致性问题的

几种形式缓存不统一的起因和解决方案

计划一 先更新数据库,再删缓存
这个计划的问题是什么呢?就是假如咱们更新数据胜利了 而后去删除缓存的时候失败了 这就导致了缓存中是老数据,会造成缓存不统一

那咱们就要保障删除肯定要胜利,咱们能够在最初删除的时候 多删除几次,第二个就是用一个中间件 canal 去兼听 mysql 的 binlog 而后 从 binlong 中解析出要删除的字段 而后 持续下面第一个的形式(这个形式的益处 全程也算是异步的跟业务代码是没有关系的)

计划二 先更新数据库,再更新缓存

这个操作 问题更多感觉 首先 更新数据胜利 更新缓存失败,或者是开始更新数据库胜利 而后更新缓存胜利 而后事务回滚,也是缓存不统一。

计划三 删除缓存 再更新数据库
看起来如同最好 我反正是删除缓存了 就算更新失败 下次去读也是最新的数据(所有看起来很美妙), 其实不然,试想 2 个并发一个更新 一个查问 你先更新的时候 删除了缓存 然而此时 查问发现没有缓存 而后吧数据缓存到了数据库 就会去查数据库 然而此时更新的又更新胜利,最初就会再很长的一个工夫内 缓存和数据库是不统一的,所以这种是计划是不可取的

综上所诉,我感觉最好的形式先查再删除 而后再配合订阅 binlong 来做多重删除的形式是不错的,可能我接触的不是很多,心愿各位大佬有更好的形式提出

说说 Redis 的淘汰策略

  • noeviction:当内存不足以包容新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以包容新写入数据时,在键空间中,移除最近起码应用的 key。
  • allkeys-random:当内存不足以包容新写入数据时,在键空间中,随机移除某个 key。
  • volatile-lru:当内存不足以包容新写入数据时,在设置了过期工夫的键空间中,移除最近起码应用的 key。
  • volatile-random:当内存不足以包容新写入数据时,在设置了过期工夫的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以包容新写入数据时,在设置了过期工夫的键空间中,有更早过期工夫的 key 优先移除。

其实我感觉用 volatile-lru 就好了 毕竟报错是齐全没有必要的 还有就是设置一个报警装置 如果不够了 就搞主从 哈哈

聊聊 redis 的长久化策略

Redis 为长久化提供了两种形式:

  • RDB:在指定的工夫距离能对你的数据进行快照存储。
  • AOF:记录每次对服务器写的操作, 当服务器重启的时候会从新执行这些命令来复原原始的数据。

聊聊 RDB

rdb 是默认的长久化形式,打个比方,你能够设置比方 90s 内 有一次写入,就长久化一次,30s 内 5 次写入就长久化一次等等,当然如果你想要禁用 RDB 配置,也是非常容易的,只须要在 save 的最初一行写上:save “”。

在 Redis 中 RDB 长久化的触发分为两种:本人手动触发与 Redis 定时触发。
针对 RDB 形式的长久化,手动触发能够应用:

  • save:会阻塞以后 Redis 服务器,直到长久化实现,线上应该禁止应用。
  • bgsave:该触发形式会 fork 一个子过程,由子过程负责长久化过程,因而阻塞只会产生在 fork 子过程的时候。

说说 AOF

appendonly yes 首先是要开启 aof。
appendfsync everysec 它其实有三种模式:

  • always:把每个写命令都立刻同步到 aof,很慢,然而很平安
  • everysec:每秒同步一次,是折中计划(默认也是这个)
  • no:redis 不解决交给 OS 来解决,十分快,然而也最不平安

AOF 的整个流程大体来看能够分为两步,一步是命令的实时写入(如果是 appendfsync everysec 配置,会有 1s 损耗),第二步是对 aof 文件的重写。

对于增量追加到文件这一步次要的流程是:命令写入 =》追加到 aof_buf =》同步到 aof 磁盘。那么这里为什么要先写入 buf 在同步到磁盘呢?如果实时写入磁盘会带来十分高的磁盘 IO,影响整体性能。

如何复原 redis 的数据呢

启动时会先查看 AOF 文件是否存在,如果不存在就尝试加载 RDB。那么为什么会优先加载 AOF 呢?因为 AOF 保留的数据更残缺,通过下面的剖析咱们晓得 AOF 基本上最多损失 1s 的数据。

说说长久化的性能实战

一些线上教训

  • 如果 Redis 中的数据并不是特地敏感或者能够通过其它形式重写生成数据,能够敞开长久化,如果失落数据能够通过其它路径补回;
  • 本人制订策略定期检查 Redis 的状况,而后能够手动触发备份、重写数据;
  • 能够退出主从机器,利用一台从机器进行备份解决,其它机器失常响应客户端的命令;

聊聊 Redis 中的 Master-Slave 模式

主从架构的特点

  • 主服务器负责接管写申请
  • 从服务器负责接管读申请
  • 从服务器的数据由主服务器复制过来。主从服务器的数据是统一的

主从架构的益处

  • 读写拆散(主服务器负责写,从服务器负责读)
  • 高可用(某一台从服务器挂了,其余从服务器还能持续接管申请,不影响服务)
  • 解决更多的并发量(每台从服务器都能够接管读申请,读 QPS 就下来了)

说说主从同步呗

主从架构的特点之一:主服务器和从服务器的数据是统一的。
主从同步的 2 种状况

残缺的同步

  • 从服务器向主服务器发送 PSYNC 命令
  • 收到 PSYNC 命令的主服务器执行 BGSAVE 命令,在后盾生成一个 RDB 文件。并用一个缓冲区来记录从当初开始执行的所有写命令。
  • 当主服务器的 BGSAVE 命令执行完后,将生成的 RDB 文件发送给从服务器,从服务器接管和载入 RBD 文件。将本人的数据库状态更新至与主服务器执行 BGSAVE 命令时的状态。
  • 主服务器将所有缓冲区的写命令发送给从服务器,从服务器执行这些写命令,达到数据最终一致性。

局部重同步

  • 主从服务器的复制偏移量 主服务器每次流传 N 个字节,就将本人的复制偏移量加上 N
  • 从服务器每次收到主服务器的 N 个字节,就将本人的复制偏移量加上 N
  • 通过比照主从复制的偏移量,就很容易晓得主从服务器的数据是否处于一致性的状态!

那你说说 redis 的高可用计划呗

Redis 个别以主 / 从形式部署(这里探讨的利用从实例次要用于备份,主实例提供读写)该形式要实现 HA 次要有如下几种计划:

  • keepalived:通过 keepalived 的虚构 IP,提供主从的对立拜访,在主呈现问题时,通过 keepalived 运行脚本将从晋升为主,待主复原后先同步后主动变为主,该计划的益处是主从切换后,应用程序不须要晓得(因为拜访的虚构 IP 不变),害处是引入 keepalived 减少部署复杂性,在有些状况下会导致数据失落
  • zookeeper:通过 zookeeper 来监控主从实例,保护最新无效的 IP,利用通过 zookeeper 获得 IP,对 Redis 进行拜访,该计划须要编写大量的监控代码
  • sentinel:通过 Sentinel 监控主从实例,主动进行故障复原,该计划有个缺点:因为主从实例地址 (IP & PORT) 是不同的,当故障产生进行主从切换后,应用程序无奈晓得新地址,故在 Jedis2.2.2 中新增了对 Sentinel 的反对,利用通过 redis.clients.jedis.JedisSentinelPool.getResource() 获得的 Jedis 实例会及时更新到新的主实例地址

那你说说 Redis 哈希槽的概念?一致性 hash 和哈希槽的概念和区别

这个问题其实就是问 再集群环境下, redis 不同的 key 存储到哪个节点的问题 ,
Redis 集群中内置了 16384 个哈希槽,当须要在 Redis 集群中搁置一个 key-value
时,redis 先对 key 应用 crc16 算法算出一个后果,而后把后果对 16384 求余数,
这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会依据节点数量大
致均等的将哈希槽映射到不同的节点。

Redis Cluster 是本人做的 crc16 的简略 hash 算法,没有用一致性 hash。Redis 的作者认为它的 crc16(key) mod 16384 的成果曾经不错了,尽管没有一致性 hash 灵便,但实现很简略,节点增删时解决起来也很不便。

聊聊分布式锁

这个话题基本上是分布式系统开发的一个必问的题目了
问你分布式锁是怎么实现的,而后大家可能就搭用它的 set NX EX 命令 而后用 lua 脚本做成一个原子性操作来实现分布式锁。其实这么搭也能够吧,而后咱们个别在生产环境的话,可能会用一些开源框架,你不如说 Redisson 来实现分布式锁。

聊聊 Redisson 是怎么实现分布式锁的

  • 第一步先尝试去加锁,返回过期工夫,如果为空则能够取得锁(返回获取锁胜利)(, 在 lua 脚本外面会判断你的 key 和 value 是不是曾经持有锁了,如果是,就是给你重试次数加,而后获取锁也是失败)
  • 如果第一次加锁失败之后,就会去判断你最大等待时间,如果走到这的时候曾经超过最大等待时间(间接返回获取锁失败,)
  • 接下来就是说我要去订阅 redis 解锁这个事件,一旦有人把锁开释就会持续告诉所有的线程去竞争锁(缩小 cpu 的损耗)
  • 而后是一个死循环的去获取锁,过后每次执行这个循环的时候,每次去获取锁之前都要去判断以后是否曾经超过最大的等待时间,如果超过了就间接开释锁。只有当取得锁,或者是最大的等待时间超过之后才会返回是否胜利获取锁的标记。(外面也是须要被告诉才持续循环)
  • 通过 Redisson 实现分布式可重入锁,比纯本人通过 set key value px milliseconds nx +lua 实现(实现一)的成果更好些,尽管基本原理都一样,因为通过剖析源码可知,RedissonLock
  • 是可重入的,并且思考了失败重试,能够设置锁的最大等待时间,在实现上也做了一些优化,缩小了有效的锁申请,晋升了资源的利用率。

完结

redis 就这些吧,接下来温习下 es 吧

日常求赞

好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是 真粉

创作不易,各位的反对和认可,就是我创作的最大能源,咱们下篇文章见

微信 搜 “ 六脉神剑的程序人生 ” 回复 888 有我找的许多的材料送给大家

正文完
 0