缓存穿透和缓存雪崩

最近发现数据库的QPS定期飙高,简单排查后,定位到原因是由于定期执行的任务,会对数据库有大量的访问。但奇怪的是,有的数据,我明明做了缓存,但是依然对数据库的请求量很大。

 

原因是,当缓存里没有我查询的数据,数据库里也没有,这时每次都会去查数据库。打个比方,你把某个DO做了缓存,key是主键,value是DO。如果你拿一个不存在于数据库里的key,去查询数据,会发生什么?

根据我在缓存与数据库的一致性思考中介绍的,首先会发现缓存里没有数据,然后取查数据库。数据库里虽然没有数据,但是我会把null放进缓存里。下次查询的时候,缓存依然没有数据,结果继续查数据库。周而复始,每次都在查数据库。

所以你会发现,问题的核心在于,不应该把null放进缓存里。而是应该放入一个特殊的对象。这样下次查询的时候,当我从缓存里查到这个特殊的对象,我就会知道,数据库里是没有值的,我不用去捞数据库了,直接返回个null出去。

以上就是所谓的缓存穿透。

缓存穿透的解决方案,除了我上面所说的之外,还有一种。就是把数据库里没有的key,维护在一个列表里。每次查询之前,先看看key在不在列表里。如果在,则也不用去查数据库了。

缓存穿透会带来什么后果呢?一是数据库的QPS会很高,而你根本不知道为什么,因为你觉得自己已经做了缓存了。二是如果别人发现你这个问题,就可以借此来攻击你,让你的数据库挂掉。

 

接着,我们来谈谈缓存雪崩。缓存雪崩的意思是,当你的缓存,在某一个时刻,发生大规模失效。例如你所有的缓存是同时加载的,而且失效时间设置的一样长。那么在缓存失效的那一刻,如果有大量的查询请求进来,这些请求都会直接打在数据库上,结果就是数据库扛不住压力,挂了。

解决的方案是:

1. 不要让缓存在某一时刻大面积失效。要么,改变缓存的失效时间,不过对于同一个缓存,这个貌似不可能(不可能我这个DO的失效时间短,那个DO的失效时间长),除非你搞两个缓存;要么,加载缓存的时间点弄得分散一些,不要同时加载缓存。

2. 不要让所有相同的请求,都打在数据库上。例如,有很多线程,都在请求某个相同的key,那么,让这些线程去抢着锁住key。锁住key的线程,去访问数据库,并更新缓存。没抢到的,则等着。这个方案的问题是,在没有雪崩的时候,你也得去抢锁,那效率就呵呵了。我感觉这个方案不怎么现实。

posted @ 2017-02-21 22:30  米其林轮船  阅读(1440)  评论(0编辑  收藏  举报