Redis
2021-03-14 16:11:49 1 举报
AI智能生成
Redis相关面试考点
作者其他创作
大纲/内容
基础
特点(为啥Redis那么快)
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速
数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
多路I/O复用模型(Linux系统),非阻塞IO
String
缓存功能:String字符串是最常用的数据类型,不仅仅是Redis,各个语言都是最基本类型,因此,利用Redis作为缓存,配合其它数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
计数器:许多系统都会使用Redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
共享用户Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,在这种模式只需要保证Redis的高可用,每次用户Session的更新和获取都可以快速完成。大大提高效率。
Hash
这个是类似 Map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 Hash 里的某个字段。
List
比如可以通过 List 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。
lrange 命令,读取某个闭区间内的元素,可以基于 List 实现分页查询,这个是很棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
Set
Set 是无序集合,会自动去重的那种。
Set 玩儿交集、并集、差集的操作,比如交集吧,我们可以把两个人的好友列表整一个交集,看看俩人的共同好友是谁
Zset
排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。<br>
用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
HyperLogLog、Geo、Pub/Sub
BloomFilter(布隆过滤器)
一般问题
<b>(1)如果有大量的key需要设置同一时间过期,一般需要注意什么?</b><br><br>如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,我们一般需要在时间上加一个随机值,使得过期时间分散一些。<br>
<b>(2)那你使用过Redis分布式锁么,它是什么回事?</b><br><br>先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。<br>
<b>(3)如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?<br><br></b>这个锁就永远得不到释放了,我记得set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!<br>
<b>(4)假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?<br><br></b>使用keys指令可以扫出指定模式的key列表。<br>
<b>(5)如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?<br><br></b>Redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。<br>
<b>(6)使用过Redis做异步队列么,你是怎么用的?<br><br></b>一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。<br>
<b>(7)如果对方追问可不可以不用sleep呢?<br></b><br>list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来<br>
<b>(8)如果对方接着追问能不能生产一次消费多次呢?<br></b><br>使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。<br>
<b>(9)如果对方继续追问 pub/su b有什么缺点?<br></b><br>在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等<br>
<b>(10)如果对方究极TM追问Redis如何实现延时队列?<br><br></b>使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。<br>
重要问题
<b>(1)Pipeline有什么好处,为什么要用pipeline?<br><br></b>可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。<br>
<b>(2)对方追问RDB的原理是什么?<br><br></b>fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。<b><br></b><br>
<b>(3)是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?<br><br></b>Redis Sentinal 着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。<br><br>Redis Cluster 着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。<br>
高级
集群
那他是单线程的,我们现在服务器都是多核的,那不是很浪费?可以通过在单机开多个Redis实例
Redis cluster 支撑 N 个 Redis master node,每个master node都可以挂载多个 slave node。
持久化
RDB 类似全量的数据,适合做冷备
优点
(1)他会生成多个数据文件,每个数据文件分别都代表了某一时刻Redis里面的数据,这种方式,有没有觉得很适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。<br><br>(2)RDB对Redis的性能影响非常小,是因为在同步数据的时候他只是fork了一个子进程去做持久化的,而且他在数据恢复的时候速度比AOF来的快。
缺点
(1)RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。AOF则最多丢一秒的数据,数据完整性上高下立判。<br><br>(2)还有就是RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒,你公司在做秒杀的时候他刚好在这个时候fork了一个子进程去生成一个大快照,哦豁,出大问题。
AOF 类似增量的数据,适合做热备
优点
RDB五分钟一次生成快照,但是AOF是一秒一次去通过一个后台的线程fsync操作,那最多丢这一秒的数据。<br><br>AOF在对日志文件进行操作的时候是以append-only的方式去写的,他只是追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。<br><br>AOF的日志是通过一个叫非常可读的方式记录的,这样的特性就适合做灾难性数据误删除的紧急恢复了
缺点
一样的数据,AOF文件比RDB还要大。<br><br>AOF开启后,Redis支持写的QPS会比RDB支持写的要低,他不是每秒都要去异步刷新一次日志嘛fsync
tip:两种机制全部开启的时候,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。,因为AOF的数据是比RDB更完整的。
<b>对方追问那如果突然机器掉电会怎样?<br><br></b>取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。<br>
哨兵
哨兵主要功能
集群监控:负责监控 Redis master 和 slave 进程是否正常工作。<br><br>消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。<br><br>故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。<br><br>配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵集群
一主二从,三个哨兵
主从同步
你启动一台slave 的时候,他会发送一个psync命令给master ,如果是这个slave第一次连接到master,他会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存,然后master会把内存里面缓存的那些新命名都发给slave。
过期策略
定期删除
默认100ms就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
惰性删除
惰性嘛,我不主动删,我懒,我等你来查询了我看看你过期没,过期就删了还不给你返回,没过期该怎么样就怎么样。
内存淘汰机制
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)<br><br>allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。<br><br>volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。<br><br>allkeys-random: 回收随机的键使得新添加的数据有空间存放。<br><br>volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。<br><br>volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
LRU算法实现
LinkedHashMap中也实现了Lru<br>
子主题
高级问题
<b>如果你多个系统同时操作(并发)Redis带来的数据问题?<br></b>系统A、B、C三个系统,分别去操作Redis的同一个Key,本来顺序是1,2,3是正常的,但是因为系统A网络突然抖动了一下,B,C在他前面操作了Redis,这样数据不就错了么。<br>就好比下单,支付,退款三个顺序你变了,你先退款,再下单,再支付,那流程就会失败,那数据不就乱了?你订单还没生成你却支付,退款了?明显走不通了,这在线上是很恐怖的事情。<br>
某个时刻,多个系统实例都去更新某个 key。可以基于 Zookeeper 实现分布式锁。每个系统通过 Zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 Key,别人都不允许读和写。<br><br>你要写入缓存的数据,都是从 MySQL 里查出来的,都得写入 MySQL 中,写入 MySQL 中的时候必须保存一个时间戳,从 MySQL 查出来的时候,时间戳也查出来。<br><br>每次要写之前,先判断一下当前这个 Value 的时间戳是否比缓存里的 Value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
<b>你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?</b>
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。<br><br>串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。<br><br>把一些列的操作都放到队列里面,顺序肯定不会乱,但是并发高了,这队列很容易阻塞,反而会成为整个系统的弱点,瓶颈
<b>Redis 和 Memcached 有啥区别,为啥选择用Redis作为你们的缓存中间件?</b>
Redis 支持复杂的数据结构:<br><br>Redis 相比 Memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, Redis 会是不错的选择。<br><br>Redis 原生支持集群模式:<br><br>在 redis3.x 版本中,便能支持 Cluster 模式,而 Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。<br><br>性能对比:<br><br>由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。
<b>Redis 的线程模型了解么?</b>
Redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。<br><br>文件事件处理器的结构包含 4 个部分:<br><br>多个 Socket<br><br>IO 多路复用程序<br><br>文件事件分派器<br><br>事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)<br><br>多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 Socket,会将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
<b>你了解最经典的KV、DB读写模式么?</b>
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern<br><br>读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。<br><br>更新的时候,先更新数据库,然后再删除缓存。
<b>为什么是删除缓存,而不是更新缓存?</b>
原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。<br><br>比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。<br><br>另外更新缓存的代价有时候是很高的。
常见的三大问题
缓冲雪崩
简单的例子
如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是我理解的缓存雪崩。
可能会引发的问题
那这个数量级别的请求直接打到数据库几乎是灾难性的,你想想如果打挂的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错,如果没做熔断等策略基本上就是瞬间挂一片的节奏
解决方法
(1)把每个Key的失效时间都加个随机值 setRedis(Key,value,time + Math.random() * 10000);<br>
(2) 设置热点数据永远不过期,有更新操作就更新缓存就好,电商首页的数据
缓冲穿透
简单的例子
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
解决方法
(1)在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等。
(2)从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null,将其缓冲起来,并设置过期时间
(3)网关层Nginx记得有配置项,可以让运维大大对单个IP每秒访问次数超出阈值的IP都拉黑。
(4)一个高级用法布隆过滤器(Bloom Filter)这个也能很好的防止缓存穿透的发生,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return
缓冲击穿
简单的例子
是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓冲,直接请求数据库,就像在一个屏幕上凿开一个洞。
解决方法
(1)设置热点数据永远不过期
(2)加上互斥锁,单机 reenLock ,集群的需要加分布式锁Lua脚本
技术总结
避免以上发生的情况
事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。<br><br>事中:本地 ehcache 缓存 + Hystrix 限流+降级,避免MySQL被打死。<br><br>事后:Redis 持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
常见面试题
缓存有哪些类型?
本地缓存
本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。
分布式缓存
分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。
多级缓存
为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
Memcache
先来看看 MC 的特点:<br><br>MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;<br><br>MC 功能简单,使用内存存储数据;<br><br>MC 的内存结构以及钙化问题我就不细说了,大家可以查看官网了解下;<br><br>MC 对缓存的数据可以设置失效期,过期后的数据会被清除;<br><br>失效的策略采用延迟失效,就是当再次使用数据时检查是否失效;<br><br>当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。<br><br>另外,使用 MC 有一些限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:<br><br>key 不能超过 250 个字节;<br><br>value 不能超过 1M 字节;<br><br>key 的最大失效时间是 30 天;<br><br>只支持 K-V 结构,不提供持久化和主从同步功能。
高级用法
Bitmap :<br>位图是支持按 bit 位来存储信息,可以用来实现 布隆过滤器(BloomFilter);<br><br>HyperLogLog:<br>供不精确的去重计数功能,比较适合用来做大规模数据的去重统计,例如统计 UV;<br><br>Geospatial:<br>可以用来保存地理位置,并作位置距离计算或者根据半径计算位置等。有没有想过用Redis来实现附近的人?或者计算最优地图路径?<br><br>这三个其实也可以算作一种数据结构,不知道还有多少朋友记得,我在梦开始的地方,Redis基础中提到过,你如果只知道五种基础类型那只能拿60分,如果你能讲出高级用法,那就觉得你有点东西。<br><br>pub/sub:<br>功能是订阅发布功能,可以用作简单的消息队列。<br><br>Pipeline:<br>可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。<br><br>Lua:<br>Redis 支持提交 Lua 脚本来执行一系列的功能。<br><br>我在前电商老东家的时候,秒杀场景经常使用这个东西,讲道理有点香,利用他的原子性。<br><br>话说你们想看秒杀的设计么?我记得我面试好像每次都问啊,想看的直接点赞后评论秒杀吧。<br><br>事务:<br>最后一个功能是事务,但 Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去。
考点
面试的时候问你缓存,主要是考察缓存特性的理解,对 MC、Redis 的特点和使用方式的掌握。<br><br>要知道缓存的使用场景,不同类型缓存的使用方式,例如:<br><br>1.对 DB 热点数据进行缓存减少 DB 压力;对依赖的服务进行缓存,提高并发性能;<br><br>2.单纯 K-V 缓存的场景可以使用 MC,而需要缓存 list、set 等特殊数据格式,可以使用 Redis;<br><br>3.需要缓存一个用户最近播放视频的列表可以使用 Redis 的 list 来保存、需要计算排行榜数据时,可以使用 Redis 的 zset 结构来保存。<br><br>要了解 MC 和 Redis 的常用命令,例如原子增减、对不同数据结构进行操作的命令等。<br><br>了解 MC 和 Redis 在内存中的存储结构,这对评估使用容量会很有帮助。<br><br>了解 MC 和 Redis 的数据失效方式和剔除策略,比如主动触发的定期剔除和被动触发延期剔除<br><br>要理解 Redis 的持久化、主从同步与 Cluster 部署的原理,比如 RDB 和 AOF 的实现方式与区别。<br><br>要知道缓存穿透、击穿、雪崩分别的异同点以及解决方案。<br><br>不管你有没有电商经验我觉得你都应该知道秒杀的具体实现,以及细节点。<br><br>加分项<br>如果想要在面试中获得更好的表现,还应了解下面这些加分项。<br><br>是要结合实际应用场景来介绍缓存的使用。例如调用后端服务接口获取信息时,可以使用本地+远程的多级缓存;对于动态排行榜类的场景可以考虑通过 Redis 的 Sorted set 来实现等等。<br><br>最好你有过分布式缓存设计和使用经验,例如项目中在什么场景使用过 Redis,使用了什么数据结构,解决哪类的问题;使用 MC 时根据预估值大小调整 McSlab 分配参数等等。<br><br>最好可以了解缓存使用中可能产生的问题。比如 Redis 是单线程处理请求,应尽量避免耗时较高的单个请求任务,防止相互影响;Redis 服务应避免和其他 CPU 密集型的进程部署在同一机器;或者禁用 Swap 内存交换,防止 Redis 的缓存数据交换到硬盘上,影响性能。再比如前面提到的 MC 钙化问题等等。<br><br>要了解 Redis 的典型应用场景,例如,使用 Redis 来实现分布式锁;使用 Bitmap 来实现 BloomFilter,使用 HyperLogLog 来进行 UV 统计等等。<br><br>知道 Redis4.0、5.0 中的新特性,例如支持多播的可持久化消息队列 Stream;通过 Module 系统来进行定制功能扩展等等。
0 条评论
下一页