(一)缓存
在我们项目中,多少是会使用缓存知识的,因为对于很多数据没必要每次都到数据库中进行查询,如果每次都到数据库中进行查询,当出现并发情况时,可能会导致数据库的宕机。比如一些经常查询但是又不经常修改的数据,我们可以直接将数据存入缓存中,以减少数据库的压力。
当然在实现缓存的时候,我们也会遇见很多问题,于是汇总一下我们在做项目的时候,常用的一些缓存问题,并提供一些解决方案以供参考。
首先我们需要理解在调用缓存的业务流程是怎样的?当用户发起一个资源的请求,我们首先会到缓存中去查询,如果缓存中没有对应的数据,之后我们再到数据库中进行查询(这里我们可以使用一个ifelse的判断),从数据库中查询到数据之后,我们要做两件事:① 将数据返回给用户 ② 将数据存放在缓存内,以便下次用户再发起资源请求的时候,我们就直接将缓存内的资源返回给用户这样就避免了再次到数据库中进行查询。
但是我们这样,就可能会出现缓存穿透、缓存击穿、缓存雪崩等等问题。
(二)缓存穿透
- 何为缓存穿透
查询不存在数据的现象我们称为缓存穿透
正常情况下,我们去查询数据都是存在。那么请求去查询一条压根数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去。
- 缓存穿透所带来的问题
数据库宕掉
如果有黑客会对你的系统进行攻击,拿一个不存在的id 去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉。
- 解决方法
缓存空值
缓存中没有存储这些空数据的key。从而导致每次查询都到数据库去了。
这些key 设置的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null ,就不用在到 数据库中去走一圈了。最后再设置过期时间。
BloomFilter
BloomFilter 类似于一个hase set 用来判断某个元素(key)是否存在于某个集合中
在缓存之前在加一层 BloomFilter ,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查 缓存 -> 查 DB。
(三)缓存击穿
-
什么是缓存击穿
在平常高并发的系统中,大量的请求同时查询一个 key 时,此时 这个key 正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们成为击穿。例如:秒杀。 -
缓存击穿会带来的问题
某一时刻数据库请求量过大,压力剧增。 -
解决方法
使用互斥锁,锁住查询的方法
因为缓存击穿是由多个线程同时去查询数据库的这条数据,我们直接在第一个查询数据的请求上使用互斥锁锁住这个方法,其他线程就再访问的时候,就获取不到锁,等第一个线程查询到了数据,然后做缓存。
(四)缓存雪崩
- 什么是缓存雪崩
当某一时刻发生大规模的缓存失效的情况。比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上面。结果就是DB 承受不住,宕掉。 - 解决办法
事前:使用集群缓存,保证缓存服务的高可用
在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。
事中:使用 ehcache 本地缓存 + Hystrix 限流&降级 ,避免 MySQL 被打死的情况发生
使用 ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵。
使用 Hystrix 进行 限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000 个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑,然后去调用我们自己开发的降级组件(降级)。比如设置的一些默认值呀之类的。以此来保护最后的 MySQL 不会被大量的请求给打死。
事后:开启 Redis 持久化机制,尽快恢复缓存集群
一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。