Redis面试题汇总(附答案),面试突击专用

本文的面试题以下:程序员

  • Redis 持久化机制
  • 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
  • 热点数据和冷数据是什么
  • Memcache与Redis的区别都有哪些?
  • 单线程的redis为何这么快
  • redis的数据类型,以及每种数据类型的使用场景,Redis 内部结构
  • redis的过时策略以及内存淘汰机制
  • Redis 为何是单线程的,优势
  • 如何解决redis的并发竞争key问题
  • Redis 集群方案应该怎么作?都有哪些方案?
  • 有没有尝试进行多机redis 的部署?如何保证数据一致的?
  • 对于大量的请求怎么样处理
  • Redis 常见性能问题和解决方案?
  • 讲解下Redis线程模型
  • 为何Redis的操做是原子性的,怎么保证原子性的?
  • Redis事务
  • Redis实现分布式锁
  • 总结
    小编整理了一套大厂Redis高频面试题,关注公众号:程序员白楠楠获取!

    Redis 持久化机制

Redis是一个支持持久化的内存数据库,经过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后经过把硬盘文件从新加载到内存,就能达到恢复数据的目的。面试

实现:单首创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,而后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,而后子进程退出,内存释放。redis

RDB是Redis默认的持久化方式。按照必定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,经过配置文件中的save参数来定义快照的周期。( 快照能够是其所表示的数据的一个副本,也能够是数据的一个复制品。) AOF:Redis会将每个收到的写命令都经过Write函数追加到文件最后,相似于MySQL的binlog。当Redis重启是会经过从新执行文件中保存的写命令来在内存中重建整个数据库的内容。算法

当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。spring

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题

缓存雪崩

缓存雪崩咱们能够简单的理解为:因为原有缓存失效,新缓存未到期间数据库

(例如:咱们设置缓存时采用了相同的过时时间,在同一时刻出现大面积的缓存过时),全部本来应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存形成巨大压力,严重的会形成数据库宕机。从而造成一系列连锁反应,形成整个系统崩溃。设计模式

解决办法:

大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。缓存

缓存穿透

缓存穿透是指用户查询数据,在数据库没有,天然在缓存中也不会有。这样就致使用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,而后返回空(至关于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是常常提的缓存命中率问题。安全

解决办法:

最多见的则是采用布隆过滤器,将全部可能存在的数据哈希到一个足够大的bitmap中,一个必定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。服务器

另外也有一个更为简单粗暴的方法,若是一个查询返回的数据为空(无论是数据不存在,仍是系统故障),咱们仍然把这个空结果进行缓存,但它的过时时间会很短,最长不超过五分钟。经过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。若是这些数据是一些32bit大小的数据该如何解决?若是是64bit的呢?

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。

Bitmap:典型的就是哈希表

缺点是,Bitmap对于每一个元素只能记录1bit信息,若是还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐)

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。

它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。

Bloom-Filter算法的核心思想就是利用多个不一样的Hash函数来解决“冲突”。

Hash存在一个冲突(碰撞)的问题,用同一个Hash获得的两个URL的值有可能相同。为了减小冲突,咱们能够多引入几个Hash,若是经过其中的一个Hash值咱们得出某元素不在集合中,那么该元素确定不在集合中。只有在全部的Hash函数告诉咱们该元素在集合中时,才能肯定该元素存在于集合中。这即是Bloom-Filter的基本思想。

Bloom-Filter通常用于在大数据量的集合中断定某元素是否存在。

缓存穿透与缓存击穿的区别

缓存击穿:是指一个key很是热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。

解决方案:在访问key以前,采用SETNX(set if not exists)来设置另外一个短时间key来锁住当前key的访问,访问结束再删除该短时间key。

给一个我公司处理的案例:背景双机拿token,token在存一份到redis,保证系统在token过时时都只有一个线程去获取token;线上环境有两台机器,故使用分布式锁实现。

缓存预热

缓存预热这个应该是一个比较常见的概念,相信不少小伙伴都应该能够很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就能够避免在用户请求的时候,先查询数据库,而后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

  • 直接写个缓存刷新页面,上线时手工操做下;
  • 数据量不大,能够在项目启动的时候自动进行加载;
  • 定时刷新缓存;

缓存更新

除了缓存服务器自带的缓存失效策略以外(Redis默认的有6中策略可供选择),咱们还能够根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

  • 定时去清理过时的缓存;
  • 当有用户请求过来时,再判断这个请求所用到的缓存是否过时,过时的话就去底层系统获得新数据并更新缓存。

二者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪一种方案,你们能够根据本身的应用场景来权衡。

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然须要保证服务仍是可用的,即便是有损服务。系统能够根据一些关键数据进行自动降级,也能够配置开关实现人工降级。

降级的最终目的是保证核心服务可用,即便是有损的。并且有些服务是没法降级的(如加入购物车、结算)。

以参考日志级别设置预案:

  • 通常:好比有些服务偶尔由于网络抖动或者服务正在上线而超时,能够自动降级;
  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),能够自动降级或人工降级,并发送告警;
  • 错误:好比可用率低于90%,或者数据库链接池被打爆了,或者访问量忽然猛增到系统能承受的最大阀值,此时能够根据状况自动降级或者人工降级;
  • 严重错误:好比由于特殊缘由数据错误了,此时须要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,致使数据库跟着一块儿发生雪崩问题。所以,对于不重要的缓存数据,能够采起服务降级策略,例如一个比较常见的作法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

热点数据和冷数据是什么

热点数据,缓存才有价值

对于冷数据而言,大部分数据可能尚未再次访问到就已经被挤出内存,不只占用内存,并且价值不大。频繁修改的数据,看状况考虑使用缓存

对于上面两个例子,寿星列表、导航信息都存在一个特色,就是信息修改频率不高,读取一般很是高的场景。

对于热点数据,好比咱们的某IM产品,生日祝福模块,当天的寿星列表,缓存之后可能读取数十万次。再举个例子,某导航产品,咱们将导航信息,缓存之后可能读取数百万次。

数据更新前至少读取两次, 缓存才有意义。这个是最基本的策略,若是缓存尚未起做用就失效了,那就没有太大价值了。

那存不存在,修改频率很高,可是又不得不考虑缓存的场景呢?有!好比,这个读取接口对数据库的压力很大,可是又是热点数据,这个时候就须要考虑经过缓存手段,减小数据库的压力,好比咱们的某助手产品的,点赞数,收藏数,分享数等是很是典型的热点数据,可是又不断变化,此时就须要将数据同步保存到Redis缓存,减小数据库压力。

Memcache与Redis的区别都有哪些?

1)、存储方式 Memecache把数据所有存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,redis能够持久化其数据

2)、数据支持类型 memcached全部的值均是简单的字符串,redis做为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储

3)、使用底层模型不一样 它们之间底层实现方式 以及与客户端之间通讯的应用协议不同。Redis直接本身构建了VM 机制 ,由于通常的系统调用系统函数的话,会浪费必定的时间去移动和请求。

4). value 值大小不一样:Redis 最大能够达到 512M;memcache 只有 1mb。

5)redis的速度比memcached快不少

6)Redis支持数据的备份,即master-slave模式的数据备份。

单线程的redis为何这么快

(一)纯内存操做

(二)单线程操做,避免了频繁的上下文切换

(三)采用了非阻塞I/O多路复用机制

Redis的数据类型,以及每种数据类型的使用场景

回答:一共五种

(一)String

这个其实没啥好说的,最常规的set/get操做,value能够是String也能够是数字。通常作一些复杂的计数功能的缓存。

(二)hash

这里value存放的是结构化的对象,比较方便的就是操做其中的某个字段。博主在作单点登陆的时候,就是用这种数据结构存储用户信息,以cookieId做为key,设置30分钟为缓存过时时间,能很好的模拟出相似session的效果。

(三)list

使用List的数据结构,能够作简单的消息队列的功能。另外还有一个就是,能够利用lrange命令,作基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST能够很好的完成排队,先进先出的原则。

(四)set

由于set堆放的是一堆不重复值的集合。因此能够作全局去重的功能。为何不用JVM自带的Set进行去重?由于咱们的系统通常都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个作一个全局去重,再起一个公共服务,太麻烦了。

另外,就是利用交集、并集、差集等操做,能够计算共同喜爱,所有的喜爱,本身独有的喜爱等功能。

(五)sorted set

sorted set多了一个权重参数score,集合中的元素可以按score进行排列。能够作排行榜应用,取TOP N操做。

Redis 内部结构

  • dict 本质上是为了解决算法中的查找问题(Searching)是一个用于维护key和value映射关系的数据结构,与不少语言中的Map或dictionary相似。本质上是为了解决算法中的查找问题(Searching)
  • sds sds就等同于char * 它能够存储任意二进制数据,不能像C语言字符串那样以字符’\0’来标识字符串的结 束,所以它必然有个长度字段。
  • skiplist (跳跃表) 跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现,
  • quicklist
  • ziplist 压缩表 ziplist是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,

Redis的过时策略以及内存淘汰机制

redis采用的是按期删除+惰性删除策略。

为何不用定时删除策略?

定时删除,用一个定时器来负责监视key,过时则自动删除。虽然内存及时释放,可是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,所以没有采用这一策略.

按期删除+惰性删除是如何工做的呢?

按期删除,redis默认每一个100ms检查,是否有过时的key,有过时key则删除。须要说明的是,redis不是每一个100ms将全部的key检查一次,而是随机抽取进行检查(若是每隔100ms,所有key进行检查,redis岂不是卡死)。所以,若是只采用按期删除策略,会致使不少key到时间没有删除。

因而,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key若是设置了过时时间那么是否过时了?若是过时了此时就会删除。

采用按期删除+惰性删除就没其余问题了么?

不是的,若是按期删除没删除key。而后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会愈来愈高。那么就应该采用内存淘汰机制。

在redis.conf中有一行配置

maxmemory-policy volatile-lru1

该配置就是配内存淘汰策略的(什么,你没配过?好好检讨一下本身)

  • volatile-lru:从已设置过时时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过时时间的数据集(server.db[i].expires)中挑选将要过时的数据淘汰
  • volatile-random:从已设置过时时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据,新写入操做会报错

ps:若是没有设置 expire 的key, 不知足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

Redis 为何是单线程的

官方FAQ表示,由于Redis是基于内存的操做,CPU不是Redis的瓶颈,Redis的瓶颈最有多是机器内存的大小或者网络带宽。

既然单线程容易实现,并且CPU不会成为瓶颈,那就瓜熟蒂落地采用单线程的方案了(毕竟采用多线程会有不少麻烦!)Redis利用队列技术将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操做(很是快速)

2)采用单线程,避免了没必要要的上下文切换和竞争条件

3)非阻塞IO优势:

  • 速度快,由于数据存在内存中,相似于HashMap,HashMap的优点就是查找和操做的时间复杂度都是O(1)
  • 支持丰富数据类型,支持string,list,set,sorted set,hash
  • 支持事务,操做都是原子性,所谓的原子性就是对数据的更改要么所有执行,要么所有不执行
  • 丰富的特性:可用于缓存,消息,按key设置过时时间,过时后将会自动删除如何解决redis的并发竞争key问题

同时有多个子系统去set一个key。这个时候要注意什么呢?

不推荐使用redis的事务机制。由于咱们的生产环境,基本都是redis集群环境,作了数据分片操做。你一个事务中有涉及到多个key操做的时候,这多个key不必定都存储在同一个redis-server上。所以,redis的事务机制,十分鸡肋。

  • 若是对这个key操做,不要求顺序:准备一个分布式锁,你们去抢锁,抢到锁就作set操做便可
  • 若是对这个key操做,要求顺序:分布式锁+时间戳。假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现本身的valueA的时间戳早于缓存中的时间戳,那就不作set操做了。以此类推。
  • 利用队列,将set方法变成串行访问也能够redis遇到高并发,若是保证读写key的一致性

对redis的操做都是具备原子性的,是线程安全的操做,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。

Redis 集群方案应该怎么作?都有哪些方案?

1.twemproxy,大概概念是,它相似于一个代理方式, 使用时在本须要链接 redis 的地方改成链接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。

缺点:twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据没法自动移动到新的节点。

2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变状况下,旧节点数据可恢复到新 hash 节点

3.redis cluster3.0 自带的集群,特色在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

有没有尝试进行多机redis 的部署?如何保证数据一致的?

主从复制,读写分离

一类是主数据库(master)一类是从数据库(slave),主数据库能够进行读写操做,当发生写操做的时候自动将数据同步到从数据库,而从数据库通常是只读的,并接收主数据库同步过来的数据,一个主数据库能够有多个从数据库,而一个从数据库只能有一个主数据库。

对于大量的请求怎么样处理

redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;

redis是经过IO多路复用(select,epoll, kqueue,依据不一样的平台,采起不一样的实现)来处理多个客户端请求的

Redis 常见性能问题和解决方案?

(1) Master 最好不要作任何持久化工做,如 RDB 内存快照和 AOF 日志文件

(2) 若是数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和链接的稳定性, Master 和 Slave 最好在同一个局域网内

(4) 尽可能避免在压力很大的主库上增长从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

讲解下Redis线程模型

文件事件处理器包括分别是套接字、 I/O 多路复用程序、 文件事件分派器(dispatcher)、 以及事件处理器。使用 I/O 多路复用程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不一样的事件处理器。

当被监听的套接字准备好执行链接应答(accept)、读取(read)、写入(write)、关闭(close)等操做时, 与操做相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字以前关联好的事件处理器来处理这些事件。

I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。

工做原理:

I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。

尽管多个文件事件可能会并发地出现, 但 I/O 多路复用程序老是会将全部产生事件的套接字都入队到一个队列里面, 而后经过这个队列, 以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字:

当上一个套接字产生的事件被处理完毕以后(该套接字为事件所关联的事件处理器执行完毕), I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。若是一个套接字又可读又可写的话, 那么服务器将先读套接字, 后写套接字.

image

为何Redis的操做是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操做的不能够再分,操做要么执行,要么不执行。

Redis的操做之因此是原子性的,是由于Redis是单线程的。(Redis新版本已经引入多线程,这里基于旧版本的Redis)

Redis自己提供的全部API都是原子操做,Redis中的事务实际上是要保证批量操做的原子性。

多个命令在并发中也是原子性的吗?

不必定, 将get和set改为单命令操做,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

Redis事务

Redis事务功能是经过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis会将一个事务中的全部命令序列化,而后按顺序执行。

  1. redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 因此 Redis 的内部能够保持简单且快速。
  2. 若是在一个事务中的命令出现错误,那么全部的命令都不会执行;
  3. 若是在一个事务中出现运行错误,那么正确的命令会被执行。

注:redis的discard只是结束本次事务,正确命令形成的影响仍然存在.

1)MULTI命令用于开启一个事务,它老是返回OK。MULTI执行以后,客户端能够继续向服务器发送任意多条命令,这些命令不会当即被执行,而是被放到一个队列中,当EXEC命令被调用时,全部队列中的命令才会被执行。

2)EXEC:执行全部事务块内的命令。返回事务块内全部命令的返回值,按命令执行的前后顺序排列。当操做被打断时,返回空值 nil 。

3)经过调用DISCARD,客户端能够清空事务队列,并放弃执行事务, 而且客户端会从事务状态中退出。

4)WATCH 命令能够为 Redis 事务提供 check-and-set (CAS)行为。能够监控一个或多个键,一旦其中有一个键被修改(或删除),以后的事务就不会执行,监控一直持续到EXEC命令。

Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的链接并不存在竞争关系Redis中可使用SETNX命令实现分布式锁。

将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不作任何动做

image

解锁:使用 del key 命令就能释放锁

解决死锁:

  • 经过Redis中expire()给锁设定最大持有时间,若是超过,则Redis来帮咱们释放锁。
  • 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就能够实现。

    总结

    欢迎关注公众号:程序员白楠楠 ,领取pdf文档的Java核心知识点总结!

这些资料的内容都是面试时面试官必问的知识点,篇章包括了不少知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。

你们一块儿交流,喜欢文章记得关注我点个赞哟,感谢支持!

相关文章
相关标签/搜索