Redis的缓存穿透、缓存击穿、缓存雪崩、热点数据失效问题及解决方案(转)
转:https://zhuanlan.zhihu.com/p/152223694
在我们的平常的项目中多多少少都会使用到缓存,因为一些数据我们没有必要每次查询的时候都去查询到数据库。特别是高 QPS 的系统,每次都去查询数据库,对于你的数据库来说将是灾难。但缓存使用不当,也会引起灾难。
缓存穿透
什么是缓存穿透
正常情况下,我们去查询的数据都是存在。但如果请求去查询一条数据库根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去。这种查询不存在数据的现象称为缓存穿透。
缓存穿透带来的问题
如果有黑客对你的系统进行攻击,拿一个不存在的id 去查询数据,会产生大量的请求到数据库中,可能会导致你的数据库由于压力太大而宕机。
解决方案
1、缓存空值
之所以会穿透,是因为缓存中没有存储这些空数据的key,从而导致每次查询都到数据库去了。因此我们可以为这些key对应的值设置null丢到缓存里面去,后面再出现查询这个key的请求的时候,就直接返回null。不过要设置过期时间。
2、布隆过滤器
这种方式在大数据场景应用比较多,比如Hbase中使用它去判断数据是否在磁盘上,还有在爬虫场景判断URL是否已经被爬取。
这种方案可以加在第一种方案中,在缓存之前再加一层布隆过滤器,在查询的时候先去布隆过滤器查询key是否存在,如果不存在就直接返回,存在再走查缓存和数据库。
3、用户鉴权
这种情况有可能是黑客进行恶意攻击,因此我们可以在系统中增加用户鉴权校验或者在接口层增加校验,直接拦截不正常的请求。
方案选择
对于一些恶意攻击,攻击带过来的大量的key是不存在的,那么我们采用第一种方案就会缓存大量不存在key的数据,此时第一种方案就不合适了,我们可以先使用第二种方案过滤掉这些key。即针对这种key异常多、请求重复率比较低的数据,我们没有必要进行缓存,使用第二种方案直接过滤掉。
缓存击穿
什么是缓存击穿
在平常高并发的系统中,大量的请求同时查询一个key时,此时这个key刚好失效了,就会导致大量的请求打到数据库上面去,这种现象我们成为缓存击穿。
缓存击穿带来的问题
会造成某一时刻数据库请求里过大,压力剧增。
解决方案
1、设置热点数据永不过期
2、加互斥锁
多个线程同时去查询数据库的这条数据时,我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它,其他线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来了,就可以直接走缓存了。
缓存雪崩
什么是缓存雪崩
缓存雪崩的情况是,在某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上。结果就是DB撑不住宕机了。
解决方案
1、事前,使用集群缓存,保证缓存服务的高可用
这种方案是在发生雪崩前对缓存集群实现高可用,如果是使用Redis,可以使用 主从+哨兵或者Redis Cluster来避免Redis全盘崩溃的情况。
2、事中:ehcache本地缓存 + Hystrix限流&降级,避免数据库被打死
使用ehcache本地缓存的目的也是考虑在Redis Cluster完全不可用的时候,ehcache本地缓存可以支撑一阵。
使用Hystrix进行限流&降级,例如一秒来了3000个请求,我们可以设置只能有一秒1000个请求能通过这个组件,那么其他剩余的2000请求就会走限流逻辑。然后去调用我们自己开发的降级组件,比如设置一些默认值之类的,以此来保护数据库不会被大量的请求给打死。
3、事后:开启Redis持久化机制,尽快恢复缓存集群
一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。
解决热点数据集中失效问题
什么是热点数据集中失效
我们在设置缓存的时候,一般会给缓存设置一个失效时间,过了这个时间,缓存就失效。对于一些热点的数据来说,当缓存失效以后会存在大量的请求过来,然后打到数据库中去,从而可能导致数据库崩溃的情况。
解决方案
1、设置不同的过期时间
为了避免这些热点数据集中失效,那么我们在设置缓存过期时间的时候,尽量让他们失效的时间错开。例如在一个基础的时间上加上或减去一个范围内的随机值。
2、互斥锁
结合上面击穿的情况,在第一个请求去查询数据库的时候加一个互斥锁,其余的查询都会被阻塞住,知道锁释放,从而保护数据库。
但是因为它会阻塞其他线程,此时系统吞吐量会下降,需要结合实际的业务去考虑是否要这么做。
原文地址:https://www.cnblogs.com/running-life-it/p/15166453.html
- 碎片化 | 第四阶段-47-值栈细节问题-视频
- word2vec 模型思想和代码实现
- 碎片化 | 第四阶段-48-hibernate概述和配置-视频
- 【LEETCODE】模拟面试-357- Count Numbers with Unique Digits
- Python 爬虫 2 爬取多页网页
- 碎片化 | 第四阶段-49-hibernate之HQL查询操作-视频
- Spring Cloud各组件超时总结
- 总结Web应用中常用的各种Cache
- 碎片化 | 第四阶段-50-hibernate之Criteria和NavtiveSQL查询操作-视频
- kafka_2.11-0.11.0.1集群搭建
- Spring Cloud Edgware新特性之五:filters端点
- 碎片化 | 第四阶段-51-Hibernate注解使用-视频
- 基础JavaScript装逼指南
- 碎片化 | 第四阶段-42-校验验证码-视频
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 解决KDE下KDE Wallet重装系统后每次登陆需要输入密码
- KDE下完美的Unity桌面体验,扔掉active window control
- C++核心准则T.5:结合使用泛型和面向对象技术应该增强它们的效果而不是成本
- C++核心准则T.10:为所有的模板参数定义概念
- C语言二级指针用法之模拟句柄用途
- Linux解压缩文件
- C++核心准则T.11:只要可能就使用标准概念
- 给pugjs的stun主题添加canvas时钟
- C++核心准则T.12:声明局部变量类型时,概念比auto更好
- Arch Linux切换rEFInd开机引导程序
- C++核心准则T.13:对于简单的,单类型参数概念,使用缩略记法更好
- VBA编写Ribbon Custom UI编辑器08——实现ZIP的写入
- 用 Python 写个七夕表白神器
- 3分钟短文 | Laravel 用户授权原来内置了这么多方法
- E0144"const char *" 类型的值不能用于初始化 "char *" 类型的实体的三种解决方法