原文:https://www.cnblogs.com/raichen/p/7750165.htm
缓存穿透
概念
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法
- 布隆过滤器。布隆过滤器原理参考之前文章。我之前在想我们的系统是分布式的,如果布隆过滤器的bitMap不准确怎么办,后来一想,本来布隆过滤器就存在误判率在这个case下面是可以容忍误判的。
- 讲不存在的key缓存一个默认值。要是有人利用伪造的ID攻击我们的应用。可以将这个不存在的key预先设定一个值。 比如,”key” , “NULL”。
在返回这个NULL值的时候,我们的应用就可以认为这是不存在的key,那我们的应用就可以决定是否继续等待继续访问,还是放弃掉这次操作。如果继续等待访问,过一个时间轮询点后,再次请求这个key,如果取到的值不再是NULL,则可以认为这时候key有值了,从而避免了大量伪造的请求访问数据库。
缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决办法
从业务层面。可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
缓存击穿(并发)
概念
高并发系统,如果一个缓存失效,存在多进程同时查询DB,同时更新缓存。这对缓存和DB都是比较大的挑战。
解决办法
- 使用互斥锁(mutex key): 这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了(如下图)
缓存加锁
如果是单机,可以用synchronized或者lock来处理,如果是分布式环境可以用分布式锁就可以了(分布式锁,可以用memcache的add, redis的setnx, zookeeper的添加节点操作)。
- "提前"使用互斥锁(mutex key):
在value内部设置1个超时值(timeout1), timeout1比实际的 timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。
- "永远不过期":
这里的“永远不过期”包含两层意思:
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
image
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。很多内存中的缓存都是基于这种方式构建的。