以下总结了关于Redis比较全面的知识笔记以及面试题,方便自己复习的同时希望对大家有所帮助。
概括
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时它也带来了一些问题,其中最大的问题就是数据的一致性问题,从严格意义上说这个问题无解。如果对数据一致要求很高,那么就不能使用缓存。
另外的一些经典的问题就是:缓存穿透、缓存击穿、缓存雪崩。没有业界也都有比较流行的解决方案。
使用缓存处理流程
首先前台发起请求,后台先去从缓存中查询数据,命中了就直接返回结果,如果缓存中没有命中就去数据库中查询数据,如果数据库中取到了数据就更新缓存,并且返回结果,如果数据库也没有取到就直接返回空结果。详细如下图所示:
缓存穿透(查不到数据)
1、概念
-
情景一:缓存穿透的概念很简单,用户想要查询一个数据,发现Redis缓存中没有,也就是缓存没有命中,于是就向数据库查询,然后发现也没有,于是本次查询失败。当用户很多的时候(秒杀场景),如果在缓存都没有命中,于是都去请求了数据库DB,一瞬间就给数据库造成巨大的压力,这种情况就是常说的缓存穿透。
-
情景二:缓存穿透就是在缓存和数据库中都没有这个数据,而用户却不断发起请求,我们数据库的id都是从1开始自增上去的,如果此时用户发起的请求id值为-1的数据或者id特别大不存在的情况。那么此时的用户很有可能是攻击者,攻击导致数据库压力过大,严重就击垮数据库。
以上两个都是造成缓存穿透的情况,原理是一致的。
2、解决方案
(1)布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。
(2)缓存空对象
当存储层不命中时,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,从而保护了后端数据源。
但是这种方式存在两个问题:
- 如果空值能够被缓存起来,这就意味着需要更多的空间存储更多的键,因为其中可能会有很多空值的键。
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口不一致的问题,这对于需要保持一致的业务会有影响。
(3)对于恶意攻击增加校验
在接口层增加校验,比如说用户的鉴权校验,参数做校验,不合法的参数直接Return,比如id做基础校验,小于0的直接拦截等等。
缓存击穿(大量数据集中一点)
1、概念
这里需要注意缓存穿透和缓存击穿的区别,缓存击穿指的是:一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿透缓存,直接请求数据库,就像在一个屏障上凿破了一个洞。
缓存击穿指的是一个Key数据非常热点,在不停的扛着高并发,大并发集中对着这一个点进行访问,当这个Key失效的瞬间,持久的大并发求穿破缓存,直接请求数据库,从而造成缓存击穿,就像在完好无损的桶上凿破了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且会写缓存,这样会导致数据库瞬间压力过大。
以上的三种说法,理解了么概念了么?
2、解决方案
(1)设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
(2)加互斥锁
分布式锁:使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其它线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩
1、概念
缓存雪崩是因为大面积的缓存在一个时间点集中失效,打崩了数据库。
举个简单的例子:如果所有首页的Key失效时间都是12小时,中午12点刷新的,在零点的时候有个秒杀活动会有大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是这个时候缓存所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA(管理员)都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是缓存雪崩。
其实缓存中的数据集中过期不是致命的,比较致命的是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩一定是在某个时间段集中创建缓存,这个时候数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已,而缓存服务节点的宕机对数据库服务器造成的压力不可预知的,很有可能瞬间就把数据库压垮。
2、解决方案
(1)Redis高可用
这个思想的含义就是:既然Redis有可能会挂掉,那我多增设几台Redis,这样一台挂掉了之后其它的还可以继续工作,高可用其实就是搭建的Redis集群。将热点数据均匀分布在不同的Redis库中。
(2)限流降级
这个解决方案的思想就是:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其它线程等待。
(3)数据预热
数据预热的含义就是在正式部署之前,先把可能高访问的数据预先访问一遍,这样可能大部分的数据就能加载到缓存中。在即将发生大并发访问前手动触发加载缓存中不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀一点。
(4)在批量往Redis存数据的时候,把每个key的失效时间都加个随机值,这样保证数据不会在同一时间大面积失效。
(5)设置热点数据永不过期,有更新操作的话就更新缓存就可以了。