使用缓存业务流程大概:查询时,先查缓存——有,直接返回;无,查数据库返回。
接下来说一下缓存在实际应用中可能出现的问题:
1、缓存穿透
1.1 什么是缓存穿透?
缓存穿透:由于缓存是不命中时被动写入的,出于容错考虑,当查询一个一定不存在的数据时——存储层查不到数据不写入缓存——导致每次查询这个不存在的数据的请求都会直接到存储层查询,失去了缓存的意义。
1.2 缓存穿透带来的问题
如果黑客对你的系统进行攻击,拿一个不存在的id去查询数据,产生大量的请求到你的数据库,可能会导致你的数据库由于压力过大而宕掉。
1.3 解决办法
1.3.1 缓存空值
之所以会发生穿透,是因为缓存中没有储存这些空数据的key。从而导致每次查询都到数据库去了。
可将这些key设置为null丢到缓存里边,当出现查询这个key的请求时,返回null。这样做每次查询无需再走数据库,但是别忘了设置过期时间。
缓存空值带来的问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
1.3.2 用布隆过滤器BloomFilter
BloomFilter类似于一个hbase set用来判断某个元素(key)是否存在于某个结合中。
这种方式在大数据场景应用比较多,比如Hbase中使用它去判断数据是否在磁盘上。还有在爬虫场景判断url是否已经被爬取过。
这种方案可以加在第一种方案中,在缓存之前加一层BloomFilter:
先去BloomFilter去查询key是否存在——如果不存在就直接返回,存在再去查缓存——查数据库
1.4 如何选择
针对于一些恶意攻击,攻击带来大量key是不存在的,那么我们采用第一种方案就会缓存大量不存在的数据。此时我们采用第一种方案就不合适了,我们完全可以先使用第二种方案过滤掉这些key。
针对这些key异常多,请求多,重复率比较低的数据,我们就没有必要进行缓存,使用第二种方案直接过滤掉。
而对于空数据的key有限的,重复率比较高的,我们则可以采用第一种方式进行缓存。
2、缓存击穿
2.1 什么是缓存击穿
缓存击穿:在平时高并发的系统中,大量的请求同时查询一个key时,这个key正好失效,就会导致大量的请求都打到数据库上面去的现象。
2.2 会带来什么问题
会造成某一时刻数据请求量过大,压力剧增。
2.3 如何解决
上面现象是多个线程同时去查询数据库的这一条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。(如果是单机,可以用synchronized或者lock来处理,如果是分布式环境可以用分布式锁就可以了(分布式锁,可以用memcache的add, redis的setnx, zookeeper的添加节点操作))
SETNX key value
将 key 的值设为 value,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是SET if Not eXists的简写。
其他线程走到这一步拿不到锁就等着,等待第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有了缓存,就直接走缓存。
3、缓存雪崩
3.1 什么是缓存雪崩
缓存雪崩的情况是指:当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到数据库上面,结果就是数据库挂掉。
3.2 解决办法
3.2.1 雪崩前:使用集群缓存,保证缓存服务的高可用
这种方案就是在发生雪崩前对缓存集群,实现高可用,如果是使用Redis,可以使用(主从 + 哨兵),Redis Cluster来避免Redis全盘崩溃的情况。
3.2.2 雪崩中:ehcache本地缓存 + Hystrix限流 & 降级,避免MySQl被打死
使用ehcache本地缓存的目的也是考虑Redis Cluster完全不可用的时候,ehcache本地缓存还能够支撑一阵。
使用Hystrix进行限流 & 降级,比如一秒来了5000个请求,我们可以设置假设一秒只能有2000个请求可以通过这个组件,那么其他剩余的3000请求就会走限流逻辑。
然后去调用我们自己开发的降级组件(降级),比如设置的一些默认值等等之类的。以此来保护最后的MySQl不会被大量的请求打死。
3.2.3 雪崩后:开启Redis持久化,尽快恢复缓存集群
一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。
4、解决热点数据集中失效问题
我们在设置缓存的时候,一般会给缓存设置一个失效的时间,过了这个时间,缓存就失效了。
对于一些热点数据来说,当缓存失效后会存在大量的请求到数据库上来,从而可能导致数据库崩溃的情况。
4.1 解决办法
4.1.1 设置不同的失效时间
为了避免这些热点数据集体失效,那么我们在设置缓存过期时间的时侯,让他们失效的时间错开。比如我们可以在原有的失效时间基础上增加一个随机值。
4.1.2 互斥锁
结合上面的击穿情况,在第一个请求去查询数据库的时候对它加一个互斥锁,其余的查询请求都会被阻塞住,直到锁被释放,从而保护数据库。
但是也是由于它会阻塞其他线程,此时系统的吞吐量会下降。需要结合实际业务去考虑。