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

前言

文本已收录至我的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 有我找的许多的材料送给大家

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据