前言程序员
使用缓存能够缓解大流量压力,显著提升程序的性能。咱们在使用缓存系统时,尤为是大并发状况下,常常会遇到一些“疑难杂症”。本文总结了一些使用缓存时常见的问题及解决方案,之后在遇到这类问题时能够做为参考,在设计缓存系统的时候也应该考虑这些常见的状况。redis
为了表述方便,本文以数据库查询缓存为例,使用缓存能够减少对数据库的压力。数据库
缓存穿透缓存
咱们在使用缓存时,每每先尝试去缓存中取值,若是没有,再去数据库取值,若是数据库也没有值,则根据业务需求,返回空或者抛异常。服务器
若是用户一直访问一个数据库不存在的数据,好比id为-1的数据,就会致使每次请求都会先去缓存查一次,而后再去数据库查一次,形成严重的性能问题。这种状况就叫缓存穿透。多线程
解决方案并发
如下几种解决方案:app
对请求参数作校验,好比用户鉴权校验,id作基础校验,id <= 0的直接拦截。分布式
若是查询到数据库没有值,也将对应的key存进缓存中,value为null。这样下次查询就直接从缓存返回了。但这里的key的缓存时间应该比较短,好比30s。防止后面在数据库插入了这条数据,而用户获取不到。ide
使用布隆过滤器,判断一个key是否已经查过了,若是已经查过了,就不去数据库查询。
缓存击穿
缓存击穿指的是,一个key的访问量很是大,好比某秒杀活动,有1w/s的并发量。这个key在某一时刻过时,那这些大量的请求就会一瞬间到数据库,数据库可能会直接崩溃。
解决方案
缓存击穿的解决方案也有几种,能够配合使用:
对于热点数据,慎重考虑过时时间,确保热点期间key不会过时,甚至有些能够设置永不过时。
使用互斥锁(好比Java的多线程锁机制),第一个线程访问key的时候就锁住,等查询数据库返回后,把值插入到缓存后再释放锁,这样后面的请求就能够直接取缓存里面的数据了。
缓存雪崩
缓存雪崩指的是,在某一时刻,多个key失效。这样就会有大量的请求从缓存中获取不到值,所有到数据库。还有另外一种状况,就是缓存服务器宕机,也算作缓存雪崩。
解决方案
针对上述两种状况,缓存雪崩有两种解决方案:
对每一个key的过时时间设置一个随机值,而不是全部key都相同。
使用高可用的分布式缓存集群,确保缓存的高可用性,好比redis-cluster。
双写不一致
在使用数据库缓存的时候,读和写的流程每每是这样的:
读取的时候,先读取缓存,若是缓存中没有,就直接从数据库中读取,而后取出数据后放入缓存
更新的时候,先删除缓存,再更新数据库
所谓双写不一致,就是在发生写操做(更新)的时候或写操做以后,可能会存在数据库里面的值和缓存中的值不一样的状况。
为何更新的时候要先删除缓存,再更新数据库?由于若是先更新数据库,而后在删除缓存的时候失败了,就会形成缓存里面的值和数据库的值不一致。
然而这样并不能彻底避免双写不一致问题。假设在大并发情景下,一个线程先删除缓存,而后取更新数据库,这个时候另外一个线程去取缓存,发现没有值,因而去读数据库,而后把数据库旧的值设置进缓存。等第一个线程更新完数据库后,数据库里面就是新的值,而缓存里面是旧的值,因此就存在了数据不一致的问题。
一个比较简单的解决办法是把过时时间设置得比较低,这样就只有在缓存没过时以前存在数据不一致问题,在一些业务场景下也还能接受。
另外一种解决方案是使用队列辅助。先更新数据库,再删除缓存。若是删除失败,就放进队列。而后另外一个任务从队列中取出消息,不断去重试删除相应的key。
还有一种解决方案是使用对一个数据使用一个队列,使读写操做串行化。好比对id为n的数据创建一个队列。对这条数据的写操做,删除缓存后,放进一个队列;而后另外一个线程过来了,发现没有缓存,则把这个读操做也放进这个队列里面。
欢迎你们关注个人公种浩【程序员追风】,文章都会在里面更新,整理的资料也会放在里面。
不过这样会增长程序的复杂性,串行化也会下降程序的吞吐量,可能得不偿失。通常主流的解决方案仍是先删除缓存,再更新数据库。能够知足绝大部分需求。
最后
欢迎你们一块儿交流,喜欢文章记得点个赞哟,感谢支持!