目录
1 缓存穿透
2 缓存击穿
3 缓存雪崩
4 三者对比
5 数据库与缓存一致性问题
1 缓存穿透
- 先说一下查询数据的流程吧
我们在查询一个key的时候,首选会到Redis中取查询,如果查询到的key不存在,那么我们就会去数据库中查询这个key,如果查询到了,我们就会在缓存中更新这个key并返回数据,这样以后的请求就会在缓存中直接查询到key。但是,如果没有查询到,那么Redis中就一直没有这个key,每次请求都会去查询数据库,由于数据库IO操作要与磁盘交互,相对内存中的操作时非常慢的,倘若这些请求非常之多,那么会给服务器端带来非常大的压力,甚至导致服务器宕机,缓存穿透就是指这个问题 - 如何解决缓存穿透
- 在查询Redis缓存后没有查询到key到数据库中去查找时,我们可以做一个人为设置,在数据库中如果也没有查询到这个key,那么我们就给这个key赋一个null值保存到我们的Redis中,在后序的查询过程中,加上一个对key的null值判断逻辑即可;
- 用布隆过滤器做优先判断
2 缓存击穿
- 什么是缓存击穿
缓存并发是说当有大量的查询请求在Redis中没有查询到key,那么这时就会挨个去查询数据库,然后再挨个去更新缓存,这将给我们MySQL数据库带来不小的压力,严重情况下会导致服务器宕机。 - 如何解决
- 从缓冲并发的产生原因我们可以看到是由于多线程并发从而导致了非常多对于数据库不必要的查询,其实只需要有一个线程去查询数据库并更新缓存就足够了。所以我们在对数据库进行查询的时候的之前可以设置通过调用Redis的setnx设置一个标记位,标记成功的线程去查询数据库,而其他未标记成功的线程则在等待一段时间后再重新发起数据查询请求
- 热点数据不设置过期时间
3 缓存雪崩
- 什么是缓存雪崩
我们在开发的时大多数情况下回给我们的key设置上一个过期时间,这样通过Redis的过期键删除策略能够自动对这些key进行回收,从而回收我们的内存空间。但是,如果我们大量的key都在某一时刻过期,那么对于这些查询key的请求都会到我们的MySQL数据库中,这样就会给我们的服务器带来非常大的压力 - 如何解决
- 在对key设置过期时间时候可以采用随机策略,比如每个key的过期时间都是1-10min中的一个随机值,这样就大量key在同一时刻失效而导致缓存雪崩的问题就可以避免。同时,我们也可以直接不给我们的key设置过期时间。
- 后台开启线程定时更新缓存
- 加互斥锁只允许一个线程去更新缓存
4 三者对比
缓存穿透发生于数据库和缓存中都没有数据的情况,缓存穿透和缓存发生在数据库有数据,缓存中没有数据的情况
5 数据库与缓存一致性问题
数据库与缓存保持一致性是说当更新数据后如何使得数据库中的数据和缓存中的数据一致,来看一下几个方案
先更新数据库后更新缓存
- 请求A更新数据库value值为10
- 请求B更新数据库value值为20
- 请求B更新缓存value值为20
- 请求A更新缓存value值为10
可见最后出现了缓存和数据库的不一致性,所以这种方案是不安全的
先更新缓存再更新数据库
- 请求A更新缓存value值为10
- 请求B更新缓存value值为20
- 请求B更细数据库value值为20
- 请求A更新数据库value值为10
可见最后还是出现了数据库和缓存不一致的情况
先删除缓存再更新数据库
这种方案的意思是删除缓存后下一次由于读取不到相应的值会从数据库中加载新值
- 请求A删除缓存value10
- 请求B为读到数据,更新数据库value10到缓存中
- 请求A更新数据库value20
可见最后出现了缓存和数据库不一致的情况
先更新数据库再删除缓存
来看一种情况
- 请求A未查询到数据,到数据库中查询到value10
- 请求B更新数据库value20
- 请求B删除缓存
- 请求A更新缓存为value10
可见最后还是出现了数据库和缓存不一致的情况,但是有一点就是这种情况发生的概率可以说是非常非常小,因为请求A更新缓存是一个非常快的操作,很难说发生在请求B更新数据库之后,所以一般来说,我们认为这种方案是可以保证数据库和缓存的一致性的
删除缓存这个操作可以和更新数据库操作解耦,从而保证删除缓存操作是成功的
通过消息队列来实现删除缓存
这种方案实现了更新数据库和删除缓存操作的解耦,就是我们在更新完数据库后,将缓存删除的操作交给消息队列,这样若删除操作失败,我们还可以接着重试。
通过订阅mysql的binlog实现缓存删除
我们可以通过一个中间件订阅mysql的binlog,中间件接受到binlog后解析修改的数据然后再删除缓存