redis常见面试题

随着系统访问量的提高,复杂度的提升,响应性能成为一个重点的关注点。而缓存的使用成为一个重点。redis 作为缓存中间件的一个佼佼者,成为了面试必问项目。本文分享一下Redis几道常见的面试题:

1.缓存雪崩

1.1.缓存雪崩:

如果缓存挂掉了,那么所有的请求都会去访问数据库
在这里插入图片描述
我们都知道我们不可能把所有的数据都放在缓存中(内存昂贵且有限)。所以redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期健进行删除。
如果缓存的过期时间设置的是相同的,并且redis恰好将这部分数据全删除了。这就会导致在这段时间内,这些缓存同时失效, 全部请求都到数据库。
这就是缓存雪崩!

1.2 如何解决缓存雪崩?

1.给缓存的过期时间加个随机值,可以大幅度的减少缓存在同一时间过期。
2.双缓存,使用两个缓存,缓存A和缓存B,缓存A的失效时间是20分钟,B不设置失效时间。自己做缓存预热操作。细分如下:
从缓存A中读数据,有则直接返回。
A没有数据,从B读数据,直接返回,并且异步起一个更新线程。
更新线程同时更新缓存A和缓存B。
对于redis全部挂掉,导致所以的请求都走数据库,可以从以下方面考虑:
事发前:实现redis的高可用(主从redis+sentinel或者Redis Cluster),尽量避免redis挂掉这种情况的发生。
事发中:如果redis真的挂掉了,我们可以设置redis本地缓存(encache)+限流(hystrix),尽量避免数据库被干掉
事发后:利用redis的持久化,redis重启后,从磁盘快速恢复数据。

2.缓存穿透

2.1 什么是缓存穿透?

即黑客故意请求缓存中不存在的数据,由于缓存没有命中,并且出于容错考虑,如果从数据库中查询不到的数据则不写入缓存,这样便没有请求都去查询数据库,导致所有的数据都怼到数据库上,有可能把数据库搞垮,从而失去了缓存的意义。

在这里插入图片描述

2.2 如何解决缓存穿透

解决方案:
1.利用互斥锁,缓存失效的时候,先去获得锁,再去请求数据库,没得到锁,则休眠一段时间重试。
2.才有异步更新策略,无论key是否取到值,都直接返回。value中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读取数据库,更新缓存,需要做缓存预热操作(项目启动前先加载缓存)。
3.提供一个能迅速判断请求是否有效的拦截机制,例如,布隆过滤器,内部维护一系列合法有效的key。迅速判断出请求锁携带的key是否合法有效,如果不合法,则直接返回。

3.缓存和数据库双写一致性问题

什么是缓存和数据库双写一致性问题?
如果仅仅是查询的话缓存中数据和数据库中数据是没问题的。但是当我们要更新的时候,各种情况很可能就导致数据库的数据和缓存中不一致的情况了。
1.从理论上说,只要我们设置了键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。但是这种虽然保证了最终一致性,但是也会有问题,如果数据库的数据更新了,但是缓存没过期的话,还是会存在数据一致性的问题

2、读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先删除缓存,然后再更新数据库。
为什么不是先更新数据库再删除缓存呢?
因为如果数据库更新了,缓存删除失败的话,那么会导致数据库中是新数据,缓存中是就数据,数据就会出现不一致的情况。
先删除缓存在更新数据库。如果数据库更新失败了,那么数据库中的数据是旧数据,缓存中是空,那么数据就不会出现不一致的情况。

4.并发竞争问题

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成
而redis自己就有天然解决这个问题的CAS乐观锁方案。

方案1
利用redis自带的incr命令,具体用法看这里http://doc.redisfans.com/string/incr.html。

方案2
可以使用独占锁的方式,类似操作系统的mutex机制。(网上有例子,http://blog.csdn.net/black_ox/article/details/48972085 不过实现相对复杂,成本较高)

方案3
使用乐观锁的方式进行解决(成本较低,非阻塞,性能较高)

如何用乐观锁方式进行解决?

本质上是假设不会进行冲突,使用redis的命令watch进行构造条件。伪代码如下:

watch price

get price $price

$price = $price + 10

multi

set price $price

exec

解释一下:

watch这里表示监控该key值,后面的事务是有条件的执行,如果从watch的exec语句执行时,watch的key对应的value值被修改了,则事务不会执行。

具体看Redis的事务功能详解这篇文章里的watch命令介绍。

方案4
这个是针对客户端来的,在代码里要对redis操作的时候,针对同一key的资源,就先进行加锁(java里的synchronized或lock)。

方案5
利用redis的setnx实现内置的锁。

方案6:
1)如果对这个key的操作不要求顺序,这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就去set操作即可,比较简单。
2)如果对这个key的操作要求顺序,假设有个key1,系统A需要将这个key1设置为valueA,系统B需要将key1设置成valueB,系统C需要将key1设置成valueC。期望key1的value值按照valueA–>valueB–>valueC的顺序变化,这个时候我们在保存数据的时候需要将时间戳保存在数据库,例如时间戳格式如下:
系统A key1 {valueA:3:00}
系统B key1 {valueB:3:05}
系统C key1 {valueC:3:10}

假设系统B先抢到锁,将时间戳设置成{valueB:3:05},这个时候系统A抢到锁,发现自己的valueA时间戳早于系统valueB时间戳,那就不做set操作了,以此类推。

引自:https://www.cnblogs.com/shamo89/p/8385390.html


版权声明:本文为weixin_43375482原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。