面试前必须要知道的Redis面试题

记录一下Redis几道常见的面试题

  • 如何解决缓存雪崩?
  • 如何解决缓存穿透?
  • 如何保证缓存与数据库双写时一致的问题?

 

缓存雪崩 

Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键删除。如果缓存数据设置的过期时间是相同的,并且Redis恰好将这部分数据全部删光了。这就会导致在这段时间内,这些缓存同时失效,全部请求到数据库中。

对于“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。

  • 解决方法:在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。

对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:

  • 事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。
  • 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)
  • 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

 

缓存穿透 

缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。

解决缓存穿透也有两种方案:

  • 由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层!
  • 当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。这种情况我们一般会将空对象设置一个较短的过期时间。

 

缓存与数据库双写一致

如果仅仅查询的话,缓存的数据和数据库的数据是没问题的。但是当更新时,各种情况可能导致数据库和缓存的数据不一致。

从理论上说,只要我们设置了键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。

除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生。

 

更新操作

一般来说,执行更新操作时,我们会有两种选择:

  • 先操作数据库,再操作缓存
  • 先操作缓存,再操作数据库

首先,要明确的是,无论我们选择哪个,我们都希望这两个操作要么同时成功,要么同时失败。所以,这会演变成一个分布式事务的问题。

所以,如果原子性被破坏了,可能会有以下的情况:

  • 操作数据库成功了,操作缓存失败了。
  • 操作缓存成功了,操作数据库失败了。

如果第一步已经失败了,我们直接返回异常出去就好了,第二步根本不会执行。

 

操作缓存

操作缓存也有两种方案:

  • 更新缓存
  • 删除缓存

一般我们都是采取删除缓存缓存策略的,原因如下:

  • 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就更加容易导致数据库与缓存数据不一致问题。(删除缓存直接和简单很多)
  • 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边(体现懒加载)

基于这两点,对于缓存在更新时而言,都是建议执行删除操作!

 

先更新数据库,再删除缓存

正常的情况是这样的:

  • 先操作数据库,成功;
  • 再删除缓存,也成功;

如果原子性被破坏了:

  • 第一步成功(操作数据库),第二步失败(删除缓存),会导致数据库里是新数据,而缓存里是旧数据。
  • 如果第一步(操作数据库)就失败了,我们可以直接返回错误(Exception),不会出现数据不一致。

如果在高并发的场景下,出现数据库与缓存数据不一致的概率特别低,也不是没有:

  • 缓存刚好失效
  • 线程A查询数据库,得一个旧值
  • 线程B将新值写入数据库
  • 线程B删除缓存
  • 线程A将查到的旧值写入缓存

要达成上述情况,还是说一句概率特别低:因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

对于这种策略,其实是一种设计模式:Cache Aside Pattern

删除缓存失败的解决思路:

  • 将需要删除的key发送到消息队列中
  • 自己消费消息,获得需要删除的key
  • 不断重试删除操作,直到成功

 

先删除缓存,再更新数据库

正常情况是这样的:

  • 先删除缓存,成功;
  • 再更新数据库,也成功;

如果原子性被破坏了:

  • 第一步成功(删除缓存),第二步失败(更新数据库),数据库和缓存的数据还是一致的。
  • 如果第一步(删除缓存)就失败了,我们可以直接返回错误(Exception),数据库和缓存的数据还是一致的。

看起来是很美好,但是我们在并发场景下分析一下,就知道还是有问题的了:

  • 线程A删除了缓存
  • 线程B查询,发现缓存已不存在
  • 线程B去数据库查询得到旧值
  • 线程B将旧值写入缓存
  • 线程A将新值写入数据库

所以也会导致数据库和缓存不一致的问题。

并发下解决数据库与缓存不一致的思路:

  • 将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。

 

对比两种策略

我们可以发现,两种策略各自有优缺点:

  • 先删除缓存,再更新数据库,在高并发下表现不如意,在原子性被破坏时表现优异
  • 先更新数据库,再删除缓存(Cache Aside Pattern设计模式),在高并发下表现优异,在原子性被破坏时表现不如意

 

1、Redis分布锁是怎么实现的?
       先拿setnx来争抢锁,抢到之后再用expire给锁加一个过期时间防止锁忘记释放。
如果在setnx之后执行expire之前的进程意外crash或重启维护了,那会咋样?Set指令 有非常复杂的参数,可以同时把setnx和expire合成一条指令来用的。


2、使用过Redis做异步队列么,你是怎么用的?有什么缺点?
       一般使用list结构作为队列,rpush生成消息,lpop消费消息。当lpop没有消息的时候,要适当sleep,一会在再重试。
缺点:在消费者下线的情况下,生产者的消息会丢失,所以得使用专业的消息队列如rabbitmq等。
能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。


3、什么是缓存穿透?如何避免?什么是缓存雪崩?如何避免?
1)、一般的缓存系统,都是按照key去缓存查询,如何不存在就去对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力,这就叫缓存穿透。
2)、如何避免缓存穿透?
①对查询结果为空的情况也进行缓存,缓存时间设置短一点或者是该key对应的数据insert之后清理缓存。
②对一定不存在的key进行过滤。可以把所有可能存在的key放到一个大的bitMap中,查询时通过该BitMap过滤。
3)、缓存雪崩
①当缓存服务器重启或大量缓存集中在某一个时间段失效,这样在失效的时候会给后端系统带来很大的压力,导致系统崩溃。
4)、如何避免缓存雪崩?
①当缓存失效后,通过加锁或者队列来控制读数据库写缓存的数量。比如对某个key只允许一个线程查询数据写缓存,其他线程等待。
②做二级缓存,A1为原始缓存,A2为拷贝缓存,当A1失效时可以访问A2,A1缓存失效时间设置为短期,A2 设置为长期
③不同的key设置不同的过期时间,让缓存失效时间点尽量均匀。

更多


4、Redis中的管道有什么用?
       一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道,是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的功能。


5、怎么理解Redis事务?
       事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序的进行,事务在执行的过程中不会被其他客户端发送来的命令请求所打断。事务就是一个原子操作:事务中的命令要么全部被执行,要么全部不执行。


6、Redis事务相关的命令有哪几个?
MULTI、EXSC、DISCARD、WATCH


7、Redis key的过期时间和永久有效分别怎么设置?
EXPIRE和PERSIST命令


8、Redis如何做内存优化?
       尽可能的使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称、姓氏、邮箱、密码设置单独key,而是将这个用户的所有信息存储到一张散列表里面。


9、Redis回收进程是如何工作的?
       一个客户端运行了新的命令,添加了新的数据。Redis检查内存使用情况,如果大于maxmemory的限制,就会根据设定好的策略进行回收,一个新的命令被执行,等等。所以我们不断的穿越内存限制的边界,通过不断达到边界然后不断的回收到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。


10、Redis支持的java客户端都有哪些?官方推荐有那个?
Redisson、Jedis、lettuce等等,官方推荐使用Redisson


11、Redis和Redisson的关系?
       Redisson是一个高级的分布式协调Redis客户端,能帮助用户在分布式环境中轻松实现一些java的对象(Bloom filter、BitSet、Set、SetMultimap、ScoredSortedSet、SortedSet、Map、List、Queue、ConcurrentMap、ListMultimap、BlockingQueue、Deque、BlockingDeque、Semaphore、Lock、ReadWriteLock、AtomicLong、CountDownLatch、Publish/Subscribe、HyperLogLog)


12、Jedis和Redisson对比有什么优缺点?
       Jedis是Redis的java实现的客户端,其中API提供了比较全面的Redis命令的支持; Redisson实现了分布式和扩展的java数据结构,和Jedis相比功能比较简单,不支持字 符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用 者对Redis的关注分离,从而让使用者能够将精力更集中的放在处理业务逻辑上。


13、说说Redis哈希槽的概念?
       Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16效验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。


14、Redis集群的主从复制模型是怎样的?
       为了使在部分节点失败或者大部份节点无法通信得情况下集群仍然可用,所以集群使用 了主从复制模型,每个节点都有N-1个复制品。


15、Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味着在实际中集群在特定条件下可能会丢失写操作。


16、一个字符串类型的值能存储最大容量是多少?
521M


17、为什么Redis需要把所有数据放到内存当中?
       Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁 盘。所以Redis具有快速和数据持久化的特征,如果不将数据放到放在内存中,磁盘I/O 速度严重影响Redis的性能。在内存越来越便宜的今天,Redis将会越来越受欢迎,如 果设置自了使用的最大内存,则数据已有记录数达到内存限值后不能继续插入新值。


18、Redis集群方案应该怎么做?都有哪些方案?
1)、Codis2 目前用的最多的集群方案,基本和twemproxy一致的效果,但他支持在节点数量改变情况下,旧节点数据可以恢复到新的hash节点。
2)、Redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置节点。
3)、在业务代码层实现,起几个毫无关联的Redis实例,在代码层对key进行hash计算,然后去对应Redis实例操作数据,这种方式对hash层代码要求比较高,考虑部分包括节点失效后的替代方案,数据震荡后的自动脚本恢复,实例监控等等。


19、Redis集群方案什么情况下会导致整个集群不可用?
       有A、B、C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整 个整个集群就会以为缺少5501-11000这个范围的槽而不可用。


20、MySQL里有2000W数据,Redis中只存20W的数据,如何保证Redis中的数据都是热点 数据?
Redis内存数据集大小上升到一定大小的时候,就会实施数据淘汰策略


20、Redis有哪些适合的场景?

  • 回话缓存:购物车信息
  • 全页缓存:
  • 队列
  • 排行榜/计数器,Redis在内存中对数字进行递归增或减的操作实现的非常好。集合(set)和有序集合(sortedSet)也使得我们在执行这些操作的时候变得非常简单,Redis只是正好提供了这两种数据结构
  • 发布/订阅


21、什么是Redis?他的优缺点
       Redis是一个key-value类型的内存数据库,很像memcached,整个数据库统统加载在内 存当中进行操作,定期通过异步操作把数据flush到硬盘上进行保存。因为是纯内存操 作Redis的性能非常出色,每秒可以处理10万次读写操作,是已知性能最快的Key-Value 数据库,Redis的出色不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外 单个Value的最大限制为1G,不想memcached只能保存1MB的数据,因此Redis可以 用来实现很多有用的功能。比如说用它的list来做FIFO双向链表,实现一个轻量级的高 性能消息队列,用它的Set 可以做高性能的tag系统等等,另外Redis也可以对存入的 key-Value设置expire时间,因此也可以被当做一个功能加强版的memcached来用。
       缺点:数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。


22、Redis和memcached相比有哪些优势?

  • Memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型
  • Redis的速度比memcached快很多
  • Redis可以持久化其数据


23、Redis支持哪几种数据类型?

String :

  • 存储json类型对象
  • 计数器
  • 点赞

list(双向链表)

  • 可以使用redis的list模拟队列,堆,栈
  • 朋友圈点赞(一条朋友圈内容语句,若干点赞语句)规定:朋友圈内容的格式:1、内容: user:x:post:x content来存储;2、点赞: post:x:good list来存储;(把相应头像取出来显示)

hash(hashmap)

  • 保存对象
  • 分组


24、redis能否将数据持久化,如何实现?

将内存中的数据异步写入硬盘中,两种方式:RDB(默认)和AOF

RDB持久化原理:通过bgsave命令触发,然后父进程执行fork操作创建子进程,子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换(定时一次性将所有数据进行快照生成一份副本存储在硬盘中)

优点:是一个紧凑压缩的二进制文件,Redis加载RDB恢复数据远远快于AOF的方式。

缺点:由于每次生成RDB开销较大,非实时持久化,

AOF持久化原理:开启后,Redis每执行一个修改数据的命令,都会把这个命令添加到AOF文件中。

优点:实时持久化。

缺点:所以AOF文件体积逐渐变大,需要定期执行重写操作来降低文件体积,加载慢


25、Redis有哪几种数据淘汰策略?

  • Noevication:返回错误当内存限制达到,并且客户端尝试执行让更多的内存被使用的命令
  • Allkeys-lru:尝试回收使用最少的键(LRU),使得新添加的数据有空间存放
  • Volatile-LRU:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • Allkeys-random:回收随机的键使得新添加的数据有空间存放。
  • Volatile-random:回收随机的建使得新添加的数据有空间存放,但仅限于在过期集合的键
  • Volatile-ttl:回收过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。


26、Redis和MQ如何平滑的扩容和缩容?
       MQ的消息是存放在内存或者磁盘中的,在缩减集群数量时,必须要迁移节点的数据。 增加节点时,要重新配置集群。这么看来是无法平滑缩容和扩容的。


27、Redis和memcached有什么区别?

  • Redis相对于memcached来说拥有更丰富的数据类型,可以使用更多的复杂场景
  • Redis原生就是支持cluster集群模式的,但是memcached没有原生的集群模式,需要依靠客户端实现往集群中分片写入数据
  • Redis使用的是单核,memcached使用的是多核,所以Redis在存储小数据时候性能较高,memcached在存储大一点的数据时候性能更好。
  • Memcached在使用简单的key-value存储时内存的利用率更高,但Redis如果采用hash的结构来做存储,内存使用率较好。


28、为什么Redis单线程模型效率很高?
       首先Redis里的数据是存储在内存中,对Redis里的数据操作的时候实际上是纯内存操作;其次他的文件事件处理器的核心机制是非阻塞的IO多路复用程序;最重要的是单线程反而避免了多线程频繁上下文切换带来的损耗。

 

29、Redis的过期策略和内存淘汰机制?
1)、在我们平常使用的Redis做缓存的时候,我们经常给这个缓存设置一个过期时间,那 么大家知道如果我们在查过期后的key时是不会有数据的。
2)、那么所有过期key的数据已经被删除了么?是如何被删除的?
其实如果一个key过期了,但是数据不一定已经被删除了,因为Redis采用的是定期删 除和惰性删除。定期删除是指Redis默认会每隔100ms会随机抽取一些设置了过期时间 的key检查是否过期了,如果过期了就删除。
3)、那么为什么不遍历删除所有而是随机抽取一些呢?
因为可能Redis中放置了大量的key,如果每隔100ms都遍历,那么CPU肯定会爆炸, Redis也就GG了。
4)、那么这样的话,为什么去查过期的key的话会查不到?
其实这就是Redis的惰性删除,当你去查key的时候,Redis会检查一下这个key时是否 设置了过期时间和是否已经过期了,如果已经过期Redis会删除这个key,并且返回空。
5)、其实当内存占用过多的时候,此时会进行内存淘汰,Redis提供了几种内存淘汰策 略:

  • Noeviction:当内存不足以容纳写入数据是,新写入的数据会报错
  • Allkeys-LRU:当内存不足以容纳新写入数据时,会移除最近最少使用的key
  • Allkeys-random:当内存不足以容纳新写入数据时,会随机移除某个key
  • Volatile-LRU:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
  • Volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
  • Volatile-TTL:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先删除。

以上几个策略最常用的应该是allkeys-LRU,其实这也要根据业务场景去选择

 

30、Redis的并发竞争问题以及解决方案?
       当并发表的去set某个值时可能结果与我们期望的不同,这时我们就要采取一些措施来避免,首先我们可以采用分布锁来保证,其次我们可以采用Redis的CAS 事务。Redis使用WATCH命令实现事务的“检查再设置”(CAS)行为。作为WATCH命令的参数的键会受到Redis的监控,Redis能检测他们的变化。在执行EXEC命令之前,如果Redis检测到至少有一个键被修改,那么整个事务便会中止运行,然后EXEC命令会返回一个 null值,提醒用户事务运行失败。

 

31、缓存雪崩和穿透
1)、缓存雪崩:发生在高并发的情况下,如果Redis宕机不可用时大量的请求涌入数据库,导致数据库崩溃以至于整个数据库不可用,在这种情况下数据库会直接无法重启,因为起来会被再次打挂。所以要想避免缓存雪崩可以考虑采用以下措施:首先尽量保证Redis的高可用(可以通过主从+哨兵或者Redis cluster来提供高可用),其次可以使用一些限流降级组件(例如hystrix)来避免MySQL被打挂,最后如果不是分布式可以考虑本地缓存一些数据
2)、缓存穿透:发生在一些恶意攻击下,在有些恶意攻击中会伪造请求来访问服务器,由于伪造 的错误数据在Redis中不能找到,所以请求直接打到数据库,导致高并发吧数据库打挂,同缓存雪崩一样,在这个情况下即使重启数据库也不能解决问题。想避免这种问题可以采用以下措施:首先对请求过来的参数做合法性效验,例如用户的id不能是负数;其次可以考虑如果有参数频繁访问数据库而不能查到,可以在Redis给他搞一个空值,避免请求直接打在数据库。

 

更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值