缓存三大问题
我们都知道,Redis的Key都存在一个ttl(time to live)过期时间,Redis默认的是永不过期。Redis作为缓存,一方面可以提升速度,但也会带来三大常见的缓存问题:缓存穿透、缓存击穿和缓存雪崩。
Redis作为缓存使用,一般为后端的缓存,主要对MySQL中的热点数据作缓存,减少对数据库的访问,因为MySQL查询效率远不及Redis。一般缓存的使用方式如下图所示:
缓存运行原理:当业务系统发送数据查询请求时,首先会在缓存中查看数据是否存在,如果存在,从缓存中获取数据并返回;否则,进入数据库中查询,找到数据后再返回。
1 、缓存穿透
概念:业务系统查询缓存和数据库中都不存在的数据。当通过接口发送数据查询请求,请求的数据在缓存中没有,转而所有请求转向数据库,但是数据库中也不存在该数据。此时,缓存起不到保护数据库的作用,就像被穿透了一样,这种现象称为缓存穿透。
危害:如果黑客大量发起不存在的数据请求,海量的请求转向数据库,数据库承受不了流量洪峰,数据库压力剧增,可能导致系统瘫痪。在目前的业务系统中,比较脆弱的就是IO部分。
解决方案:
- 前端验证。对于接口的请求参数进行前端校验,验证合格后再发起后端数据查询请求。
//验证表单是否为空,若为空则将焦点聚焦在input表单上,否则表单通过,登录成功
function check(form){
var accountName = $("#accountName"),$password = $("#password");
var accountName = accountName.val(),password = $password.val();
if(!accountName || accountName == ""){
showMsg("请输入用户名");
form.accountName.focus ();
return false;
}
if(!password || password == ""){
showMsg("请输入密码");
form.password.focus ();
return false;
}
//这里为用ajax获取用户信息并进行验证,如果账户密码不匹配则登录失败,如不需要验证用户信息,这段可不写
$.ajax({
url : systemURL,// 获取自己系统后台用户信息接口
data :{"password":password,"accountName":accountName},
type : "GET",
dataType: "json",
success : function(data) {
if (data){
if (data.code == "1111") { //判断返回值,这里根据的业务内容可做调整
setTimeout(function () {//做延时以便显示登录状态值
showMsg("正在登录中...");
console.log(data);
window.location.href = url;//指向登录的页面地址
},100)
} else {
showMsg(data.message);//显示登录失败的原因
return false;
}
}
},
error : function(data){
showMsg(data.message);
}
});
}
//错误信息提醒
function showMsg(msg){
$("#CheckMsg").text(msg);
}
//监听回车键提交
$(function(){
document.onkeydown=keyDownSearch;
function keyDownSearch(e) {
// 兼容FF和IE和Opera
var theEvent = e || window.event;
var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
if (code == 13) {
$('#submit').click();//具体处理函数
return false;
}
return true;
}
});
- 缓存空值。之所以会发生缓存穿透,是因为缓存中不存在这些空数据的key,才会导致所有请求转向数据库。如果当数据库返回为空时,把这些空值对应的key缓存到Redis,并合理设置过期时间。
- 使用布隆过滤器。布隆过滤器可以用于检索一个元素是否在一个集合中,它的优点是空间效率和查询时间比一般的算法要好得多。当业务系统发出查询请求的时候,首先去布隆过滤器中查询该key是否存在。如果不存在,则说明数据库中也不存在该数据,因此不需要查询,直接返回null。如果存在,则继续执行后续的流程,先前往Redis缓存中查询,缓存中没有的话再前往数据库中的查询。
方案选择:
(1)对于黑客的恶意攻击,查询的key值往往不同,而且数据量巨大。使用缓存空值的方法不大可取,可以使用布隆过滤器进行海量请求的过滤。
(2)如果查询的空数据,key值重复率高,可以选择缓存空值的方法。
2、 缓存击穿
概念:某个热点key存在与Redis缓存中,在它过期的一瞬间,大量查询该key的请求转向数据库,造成瞬间数据库的数据达到洪峰,压力剧增。这时缓存像是被击穿了一样,称为缓存击穿现象。
危害:海量的请求转向数据库,可能导致数据库服务器宕机,整个应用系统不能正常工作。
解决方案:
- 使用互斥锁。当业务系统发出查询请求时,这时Redis缓存中对应地key失效了,会转向数据库查询,数据库查询完成后,查询完成后也会造成缓存重建重复。加上互斥锁不会发生这种现象,当第一个数据库查询请求发起后,就将缓存中该Key对应的数据上锁;此时到达缓存的其他查询请求将无法查询该字段,从而被阻塞。当第一个请求完成数据库查询,并将数据更新值缓存后,释放锁;此时其他被阻塞的查询请求将可以直接从缓存中查到该数据。但是,这样其他线程会被阻塞,导致系统的吞吐量下降。
- 设置缓存数据用不过期。这里的不过期可以是设置永不过期,或者是在数据快要过期时,有异步的线程去构建缓存的热点数据,达到永不过期的效果。
- 设置接口限流与熔断,降级。在业务系统中,重要的数据查询接口,一定要做好相应的限流措施,防止用户恶意访问,同时要进行降级准备。当接口中的某些服务不可用时,进行熔断。
方案选择:
(1)互斥锁方案,完全依赖于第一个线程进行缓存重建,其余线程查询新缓存中的数据。一旦构建缓存中过程出现问题,会出现线程死锁和线程池阻塞的风险。而且,这种方案会降低业务系统的吞吐量。
(2)设置永不过期方案,可能会出现问题,实际上已经不存在热点 key 产生的一系列危害,会存在数据不一致的情况,同时代码复杂度会增大。
3 、缓存雪崩
概念:大量的热点数据在同一时间过期,导致Redis缓存在同一时刻集体失效。同一时刻,对不同的过期key进行访问,引起雪崩的现象,造成瞬时数据库请求量大,压力骤增,数据库可能崩溃。
解决方案:
- 优化缓存过期时间。将热点数据的过期时间打散。雪崩的原因,在于同一时刻,大量热点数据同时过期,我们只要给热点数据设置过期时间加个随机值,就能解决这个问题。
- 保持Redis缓存层的高可用性。使用Redis 哨兵模式或者Redis 集群部署方式,即便个别Redis 节点宕机,整个缓存层依然可以使用。
- 设置缓存数据用不过期。与防止缓存击穿的方案一致,这里的不过期可以是设置永不过期,或者是在数据快要过期时,有异步的线程去构建缓存的热点数据,达到永不过期的效果。
- 使用互斥锁重建缓存。根据 key 去缓存层查询数据,当缓存层为命中时,对 key 加锁,然后从存储层查询数据,将数据写入缓存层,最后释放锁。若其他线程发现获取锁失败,则让线程休眠一段时间后重试。
- 使用限流和降级组件。假如有一个资源不可用,可能会造成所有线程在获取这个资源时异常,造成整个系统不可用,可以降级补充热点数据,降级在高并发系统中是非常正常的。Hystrix是一款开源的“防雪崩工具”,它通过 熔断、降级、限流三个手段来降低雪崩发生后的损失。
同一时间过期,导致Redis缓存在同一时刻集体失效。同一时刻,对不同的过期key进行访问,引起雪崩的现象,造成瞬时数据库请求量大,压力骤增,数据库可能崩溃。
解决方案:
- 优化缓存过期时间。将热点数据的过期时间打散。雪崩的原因,在于同一时刻,大量热点数据同时过期,我们只要给热点数据设置过期时间加个随机值,就能解决这个问题。
- 保持Redis缓存层的高可用性。使用Redis 哨兵模式或者Redis 集群部署方式,即便个别Redis 节点宕机,整个缓存层依然可以使用。
- 设置缓存数据用不过期。与防止缓存击穿的方案一致,这里的不过期可以是设置永不过期,或者是在数据快要过期时,有异步的线程去构建缓存的热点数据,达到永不过期的效果。
- 使用互斥锁重建缓存。根据 key 去缓存层查询数据,当缓存层为命中时,对 key 加锁,然后从存储层查询数据,将数据写入缓存层,最后释放锁。若其他线程发现获取锁失败,则让线程休眠一段时间后重试。
- 使用限流和降级组件。假如有一个资源不可用,可能会造成所有线程在获取这个资源时异常,造成整个系统不可用,可以降级补充热点数据,降级在高并发系统中是非常正常的。Hystrix是一款开源的“防雪崩工具”,它通过 熔断、降级、限流三个手段来降低雪崩发生后的损失。