Redis缓存穿透和缓存雪崩的面试题解析
前段时间去摩拜面试,然后,做笔试的时候,遇到了几道Redis面试题目,今天来做个总结。捋一下思路,顺便温习一下之前的知识,如果对您有帮助,左上角点下关注 ! 谢谢
大家都知道Redis是一个缓存中间件, 类似的 还有
- Ehcache(纯Java的进程内缓存框架,也叫二级缓存)
- memcache是一套分布式的高速缓存系统
用的最多的还是Redis,而且我个人也觉得Redis比较好用,既然使用Redis
就会必然会考虑使用Redis出现的各种紧急情况 比如、高并发 、优化、穿透、雪崩、
我们先来讲一下几个概念
缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存查不到,接着查询数据库也无法查询出结果,因此也不会写入到缓存中,这将会导致每个查询都会去请求数据库,造成缓存穿透;简单点说就是穿过了缓存,就像把玻璃打破,穿过玻璃一样
解决缓存穿透也有两种方案:
1、由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter) 或者压缩filter提前拦截,不合法就不让这个请求到数据库层!
- 缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
解决缓存穿透 | 使用场景 | 维护成本 |
---|---|---|
缓存空对象 | 1.数据命中不高 2.数据吞吐量大 | 代码维护简单,需要更多的缓存空间 数据不一致 |
布隆过滤器 | 1.数据命中不高 数据相对固定实时性低 | 代码维护复杂、缓存空间大 |
如果是电商类的项目, 这里面试官还会问,假设 有10000个请求,想达到第一次请求从数据库中获取,其他9999个请求从redis中获取这种效果。
高并发情况下,可能都要访问数据库,因为同时访问的方法,这时需要加入同步锁,当其中一个缓存获取后,其它的就要通过缓存获取数据.
+ 方法一: 在方法上加上同步锁 synchronized
//加同步锁,解决高并发下缓存穿透
@Test
public synchronized void getMyUser(){
//字符串的序列化器 redis
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//查询缓存
MyUser myUser = (MyUser) redisTemplate.opsForValue().get("myUser");
if(null == myUser){
System.out.println("当缓存没有myUser时显示.");
//缓存为空,查询数据库
myUser = myUserMapper.selectByPrimaryKey(1l);
//把数据库查询出来的数据放入redis
redisTemplate.opsForValue().set("myUser",myUser);
}
System.out.println(myUser);
}
- 方法二: 使用双层检测锁, 效率高于方法一.
@Test
public void getMyUser(){
//字符串的序列化器 redis
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//查询缓存
MyUser myUser = (MyUser) redisTemplate.opsForValue().get("myUser");
//双层检测锁
if(null == myUser){
synchronized(this){
myUser = (MyUser) redisTemplate.opsForValue().get("myUser");
if(null == myUser){
System.out.println("当缓存没有myUser时显示.");
//缓存为空,查询数据库
myUser = myUserMapper.selectByPrimaryKey(1l);
//把数据库查询出来的数据放入redis
redisTemplate.opsForValue().set("myUser",myUser);
}
}
}
System.out.println(myUser);
}
进行高并发测试:
package com.bdqn.spiritmark.controller;
import com.bdqn.spiritmark.bean.MyUser;
import com.bdqn.spiritmark.mapper.MyUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
public class MyUserController {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Resource
private MyUserMapper myUserMapper;
public Integer insertNew(){
return 0;
}
@RequestMapping("/getMyUserTest")
public void getMyUserTest(){
//线程,该线程调用底层查询MyUser的方法
Runnable runnable = new Runnable() {
@Override
public void run() {
getMyUser();
}
};
//多线程测试一下缓存穿透的问题
ExecutorService executorService = Executors.newFixedThreadPool(25);
for(int i=0;i<10000;i++){
executorService.submit(runnable);
}
}
public void getMyUser(){
//字符串的序列化器 redis
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//查询缓存
MyUser myUser = (MyUser) redisTemplate.opsForValue().get("myUser");
//双层检测锁
if(null == myUser){
System.out.println("当缓存没有myUser时显示.没有进入同步锁");
synchronized(this){
myUser = (MyUser) redisTemplate.opsForValue().get("myUser");
if(null == myUser){
System.out.println("当缓存没有myUser时显示.已经进入同步锁");
//缓存为空,查询数据库
myUser = myUserMapper.selectByPrimaryKey(1l);
//把数据库查询出来的数据放入redis
redisTemplate.opsForValue().set("myUser",myUser);
}
}
}
System.out.println(myUser);
}
}
线程池中不要特别大的线程,
随后看打印输出:
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.没有进入同步锁
当缓存没有myUser时显示.已经进入同步锁
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b49b283] was not registered for synchronization because synchronization is not active
2019-01-01 17:10:51.616 INFO 19540 --- [ool-1-thread-14] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-01-01 17:10:51.747 INFO 19540 --- [ool-1-thread-14] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1935473697 wrapping com.mysql.cj.jdbc.ConnectionImpl@4bc0a38] will not be managed by Spring
==> Preparing: select id, user_id, open_id, user_name, user_phone, location from my_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, user_id, open_id, user_name, user_phone, location
<== Row: 1, 111, 111, 123, 111, t
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b49b283]
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
MyUser(id=1, userId=111, openId=111, userName=123, userPhone=111, location=t)
...
...
可以看到多个并发同时访问方法时,只有一个进入同步锁查询了数据库,其它还是通过缓存获取数据.
缓存雪崩
缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取,如下图)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。
基本解决思路如下:
第一,大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,避免缓存失效时对数据库造成太大的压力,虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。
第二,分析用户的行为,尽量让缓存失效的时间均匀分布。
第三,如果是因为某台缓存服务器宕机,可以考虑做主备,比如:redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏数据,需要好好解决。
Redis雪崩效应的解决方案:
1、可以使用分布式锁,单机版的话本地锁
2、消息中间件方式
3、一级和二级缓存Redis+Ehchache
4、均摊分配Redis的key的失效时间
缓存并发
缓存并发是指,高并发场景下同时大量查询过期的key值、最后查询数据库将缓存结果回写到缓存、造成数据库压力过大
解决
在缓存更新或者过期的情况下,先获取锁,在进行更新或者从数据库中获取数据后,再释放锁,需要一定的时间等待,就可以从缓存中继续获取数据。
参考文章地址 :
- https://redis.io/documentation
- https://blog.csdn.net/lby0307/article/details/79680326
- https://www.cnblogs.com/George1994/p/10668889.html
Redis缓存穿透和缓存雪崩的面试题解析的更多相关文章
- Redis缓存穿透和缓存雪崩以及解决方案
Redis缓存穿透和缓存雪崩以及解决方案 Redis缓存穿透和缓存雪崩以及解决方案缓存穿透解决方案布隆过滤缓存空对象比较缓存雪崩解决方案保证缓存层服务高可用性依赖隔离组件为后端限流并降级数据预热缓存并 ...
- 预防Redis缓存穿透、缓存雪崩解决方案
最近面试中遇到redis缓存穿透.缓存雪崩等问题,特意了解下. redis缓存穿透: 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有.这样就导致用户查询的时候,在缓存中找不到,每次都要去 ...
- Redis 缓存穿透,缓存击穿,缓存雪崩的解决方案分析
设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 一.什么样的数据适合缓存? 分析一个数据是否适合缓存,我们要从访问频率.读写比例.数据一致性等要求去分析. 二.什么 ...
- redis与mysql性能对比、redis缓存穿透、缓存雪崩
写在开始 redis是一个基于内存hash结构的缓存型db.其优势在于速读写能力碾压mysql.由于其为基于内存的db所以存储数据量是受限的. redis性能 redis读写性能测试redis官网测试 ...
- Redis缓存雪崩、缓存穿透、缓存击穿、缓存降级、缓存预热、缓存更新
Redis缓存能够有效地加速应用的读写速度,就DB来说,Redis成绩已经很惊人了,且不说memcachedb和Tokyo Cabinet之流,就说原版的memcached,速度似乎也只能达到这个级别 ...
- redis缓存穿透,缓存击穿,缓存雪崩原因+解决方案
一.前言 在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是 ...
- SpringBoot微服务电商项目开发实战 --- Redis缓存雪崩、缓存穿透、缓存击穿防范
最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...
- redis缓存穿透,缓存击穿,缓存雪崩
概念解释 redis 缓存穿透 key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源.比如用一个不存在的用户id获取用户信息,不论缓存还是数据库 ...
- redis的缓存雪崩、缓存穿透和缓存击穿
缓存雪崩: 比如给缓存中的key设置了统一的过期时间,而在过期时间点,有大量的请求进来,这个时候redis中没有用户请求的资源,所以所有的请求会全部拥到数据库,如果数据库有报警监测的话,可能会报一下警 ...
- Redis缓存穿透,缓存击穿,缓存雪崩,热点Key
导读 使用Redis难免会遇到Redis缓存穿透,缓存击穿,缓存雪崩,热点Key的问题.有些同学可能只是会用Redis来存取,基本都是用项目里封装的工具类来操作.但是作为开发,我们使用Redis时可能 ...
随机推荐
- FL studio系列教程(十):FL Studio中如何新建样本
FL Studio中强调以样本为核心的编曲模式.样本其实就是一个小的音序片段,可以是单独的乐器或单独的打击乐,还可以是他们组合的一个小音序片段,它是我们学习编曲的最基础知识.所以本文主要为大家讲解的是 ...
- [LGOJ1273]有线电视网
solution 用了一个很有意思的转移方法. $dp[i][j] $ 表达 \(i\) 作为根,\(j\)个终端时最大的收益,即钱数,当\(0\leq dp[1][i]\)时,即以1为根可以转移到\ ...
- 编程C语言进阶篇——自定义数据类型:结构体
一.结构体 定义方法: 结构名 变量名 特点: 两个同类型的结构变量可以相互赋值,但是结构变量之间不能使用"<","=="等运算符,如果使用则需要对运算符 ...
- 对于Web开发最棒的22个Visual Studio Code插件
翻译 原文作者:James Quick 原文地址:https://scotch.io/bar-talk/22-best-visual-studio-code-extensions-for- ...
- Sql注入--数字型手工测试
Sql注入--数字型手工测试 漏洞原因:是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的"数据"拼接到SQL语句中后,被当作SQL语句的一部分执行. 从而 ...
- 从七牛云迁移图片到github
迁移理由 问题是网站的大部分图床都是用的七牛云,官网有改动,所以原测试域名都失效,所以决定进行迁移,将七牛云中的图片迁移到github仓库中. 迁移步骤 Step1:从废弃测试域名空间至可用测试域名空 ...
- centos7中安装redis出现的问题
重现步骤: 1.解压redis包后 执行make命令.出现一堆东西,其中有gcc:命令未找到. 解决:安装 yum install gcc-c++(需要有网) 2.安装完gcc命令后,再make.出现 ...
- moviepy音视频剪辑:使用rotate函数实现视频变换处理以及参数expand取值为True时的花屏问题解决方案
☞ ░ 前往老猿Python博文目录 ░ 一.rotate函数功能介绍 moviepy的rotate函数用于将剪辑逆时针旋转指定的角度或弧度. 调用语法:rotate(clip, angle, uni ...
- 第15.41节、PyQt(Python+Qt)入门学习:输入部件QComboBox组合框功能详解
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.概述 Designer中输入工具部件中的Combo Box组合框与 ...
- 使用pip安装的Python扩展模块是从哪里下载的?
对于初学者才开始使用Python安装扩展模块时,发现只要输入扩展模块名就可以安装,无需先下载再安装,不免疑惑那些要安装的软件是在哪里下载的?是否可以从别的地方下载? 这个问题答案如下: pip安装第三 ...