redis缓存击穿

缓存击穿:

缓存击穿是指,针对某个访问非常频繁的热点数据的请求,无法在缓存中进行处理,紧接着,访问该数据的大量请求,一下子都发送到了后端数据库,导致了数据库压力激增,会影响数据库处理其他请求。

原因:缓存击穿的情况,经常发生在热点数据过期失效时

解决方法

使用redis缓存

 

优点

设计简单,开发效率高
不会侵入业务代码,spring的aop就能很好的实现

缺点

一旦redis的数据失效,那么假如这时候有10w个请求打过来,由于redis没有缓存,那么按照缓存设计的逻辑,就会全部去查数据库

适用情况

如果一个系统是内部系统,天生没有太多的用户量,而某个接口需要非常耗时的查询,而数据变化又不会太频繁,就比较适用这种设计, 没有太多用户,就不会造成缓存失效的时候大量请求压到数据库的问题

定时任务主动刷新缓存设计

 

 

优点

1、用户的请求压力永远不会直接打到数据库上
2、查询redis的内存数据,查询效率很高

缺点

1、可能对redis内存消耗非常大,因为要提前将数据加载到缓存
增加了系统的复杂度,必须要有个非常可靠的定时任务操作,不然一旦定时任务失效,那么redis中的数据失效,对于用户来说就是服务不可用
数据的实时性非常依赖定时任务的执行频率,定时任务执行的频率高,实时性就强
2、数据不能保证实时刷新,如果刷新缓存的间隔设置很长,那么数据实时性就不够好,如果刷新缓存的间隔很短,那么频繁的全量刷数据库到缓存对系统和数据库都是压力,也会让数据库和应用服务器的负载变得不够平稳
3、由于是只查询缓存,所以会对业务代码进行较大程度的改动,后期业务变化,可能会非常难以维护

适用情况

1、已有一套现成的高可靠分布式定时任务系统
2、查询的数据变化不大
3、用户的请求量非常大的情况下

使用redis的分布式锁

 

查询逻辑

如果缓存命中直接返回数据集
如果缓存没有,则尝试获取分布式锁(有超时设置)
如果没有拿到锁,则阻塞当前线程,n秒,之后再次尝试获取分布式锁(自旋,轮询,浪费CPU)
拿到锁之后检查数据是否已经被其他线程放到redis缓存中,如果redis缓存已有,直接返回redis中的数据,释放分布式锁
如果缓存没有被刷新,则查数据库
将数据库查询的结果保存到redis缓存中
返回查询结果

优点

1、数据的实时性较高,不需要其他外部系统依赖,利用了redis自己的特性,实现分布式锁,保证了同样的数据库查询同时只会查询1次,对数据库的压力较小
2、不会侵入业务代码,spring的aop就能很好的实现

缺点

1、由于阻塞等待分布式锁是个自旋阻塞操作,所以其实对应用服务器来说非常浪费cpu的分片时间,如果这时候大量请求打过来, 应用服务器反而会先扛不住,因为这里会有大量的线程在自旋占用CPU,如果用户的查询是由多个系统的结果构成,每个系统的查询依赖上一个系统查询的结果,各个查询是串行的,那么自旋的睡眠时间可能会成为拖慢请求的罪魁祸首,多个系统都这么设计都在自旋睡眠,明显效率很低

适用情况

如果要求保证数据库的压力特别小,同样的请求只能查询一次数据库,
而且服务器较多,足以将多个请求分散到不同服务器,不至于造成太多线程自旋,那么可以使用这样的设计,但不推荐,因为这种自旋操作真的不是个好设计

普通加jvm的锁查询缓存

查询逻辑

如果缓存命中直接返回数据集
如果缓存没有,则尝试JVM锁,其他线程阻塞
拿到锁之后,检查redis是否有数据,以免其他线程已经刷过缓存
如果redis已经有数据,直接返回,并释放锁,返回数据库结束
如果redis没有数据,则查询数据库,并保存到redis缓存中
返回数据,释放锁
设有s台服务器,用户请求数为n
那么同一时间参数相同的请求最多只会有s次查询打到数据库上,这里s这个常量
相当于原来对于数据库来说一个O(n)的操作时间下降到了O(s)
这里可以看出,查询数据库操作的耗时与n的增长无关,只与s有关

想象一下,我们有4台服务器,本来打到数据库上可能有10w个查询,但是因为我们使用了jvm的锁,每台服务器只会查询一次,总的数据库查询次数下降到了4次,是不是很高效?而且jvm提供的锁一定比redis分布式锁自旋轮询高效太多!

优点

1、数据的实时性较高
相对于使用redis分布式锁,大幅降低服务器资源的消耗,jvm的锁效率要高很多
2、对于数据库的消耗较小,是一个和服务器数量s相关的耗时操作,与请求数量n无关(n可能会很大,十万,百万级别,而s可能最多两位数)
如果mysql数据库版本较低,说不定还能利用上mysql数据库的缓存,如果是个不频繁更新的表,运气好的情况下s-1次的查询可能都会命中mysql的缓存
3、实现的复杂度低
4、不会侵入业务代码,spring的aop就能很好的实现

缺点

1、对数据库查询虽然减小到了一个只与服务器数量相关的函数,但依然有冗余(其实也还好了)

适用情况

如果能容忍较少次数的数据库重复查询
这种设计就用这种就已经能很好的解决缓存穿透的问题了,而且设计简单复杂度低
复杂度低意味着系统的稳定

jvm缓存+redis缓存的多级缓存

 

查询的逻辑如图所示

二级缓存的关键在于:

jvm的缓存时间是个随机值,比如 10秒~30秒
这种设计,服务器只会在jvm缓存失效,且redis缓存也失效的情况下才会查询数据库,而多个服务器的jvm缓存失效时间是随机值,所以很大程度上避免的同时失效去查库的情况,由于所有服务器jvm缓存同时失效redis缓存也失效的可能性极低,所以数据库上重复的查询会很少
(不一定是jvm缓存和jvm的锁啊,python,go同理)
设服务器的台数为s

如何让O(s)的问题其变为O(1)呢?其实也是有办法的,就是多级缓存
就是让每台服务器上加一个jvm的缓存在redis之前
这个jvm的缓存时间需要设置一个随机值,比如 缓存时间为 5s-10s,这样可以很大程度避免在redis失效的时候,每台服务器都需要去做更新redis缓存的操作,因为每个服务器的jvm缓存失效时间是不一样的

优点

1、数据的实时性较高 (设置合适的jvm缓存过期时间和redis缓存过期时间)
2、几乎没有冗余的数据库查询
3、绝大多数查询是使用的jvm缓存,效率极高
4、对cpu的占用很低
5、不会侵入业务代码,spring的aop就能很好的实现

缺点

1、如果查询的参数离散度较高,其实会很浪费业务服务器的内存空间(但是可以通过减少jvm缓存的时间来优化一点)
2、设计稍微有点复杂,需要有经验的工程师来实现

适用情况

几乎可以支持所有情况,强力推荐

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值