很多使用场景,查询的缓存数据都是由定时任务取刷新,然后缓存查不到从 DB 查了在更新缓存。如果这些 key 在同一时间失效, 那么失效的时候,大量的请求过来。就会直接打到 DB 上, 这个时候 DB 很可能被打垮,即使马上重启也会被新的流量打垮。
这种同一时间大量缓存的失效,导致请求直接打到 DB 上的情况, 就是缓存雪崩。
针对于这种情况,在批量写 Redis 数据的时候, 从缓存的过期时间入口,将原来的固定过期时间,调整为过期时间=基础时间+随机时间
,让缓存慢慢过期,避免瞬间全部过期,对DB产生过大压力。
缓存穿透指的是缓存缓存和数据库中都没有的数据,而用户不断发起请求,让请求直接落再DB上,增加数据库压力,可能导致数据库被击垮。这种请求很可能是攻击者恶意发起的。
针对这种情况首先要做好数据校验,不合法的数据尽量拦截掉.
在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿。
针对这种情况可以:
对于某些 key 有非常大的访问量, 容易把 hot key 所在的那台机器打垮。
解决方案:
当访问缓存时,如果key对应的value过大,读写、加载很容易超时,容易引发网络拥堵。另外缓存的字段较多时,每个字段的变更都会引发缓存数据的变更,频繁的读写,导致慢查询。如果大key过期被缓存淘汰失效,预热数据要花费较多的时间,也会导致慢查询。
解决方案:
在另一篇博客有详细的介绍: MySQL与缓存一致性问题
主要的缓存数据淘汰算法有三种:
先入先出算法的核心思想是: 最先进来的数据,被认为在未来被访问的概率也是最低的,所以优先淘汰最早进来的数据。
优点: 简单公平, 易于实现
缺点: 缓存命中率低, 常用数据容易被淘汰掉
最近最久未使用: 如果一个数据最近很少被访问到,那么被认为在未来被访问的概率也是最低的,当规定空间用尽且需要放入新数据的时候,会优先淘汰最久未被访问的数据
优点:
缺点:
最简单的 LRU 算法, 对偶发性、周期性的数据没有良好的抵抗力,很容易就造成缓存的污染(偶发数据占据内存淘汰热点数据),影响命中率。
因此衍生出了 LRU-K
、Two Queues
等变种,目的就是当判别数据为偶发或周期的冷数据时,不会存入空间内,从而降低热数据的淘汰率。
如上图,LRU-K有两个队列,新来的元素先进入到历史访问队列中,该队列用于记录元素的访问次数,采用的淘汰策略是LRU或者FIFO,当历史队列中的元素访问次数达到K的时候,才会进入缓存队列。 K 是指数据被访问 K 次,传统LRU与此对比则可以认为传统 LRU 是 LRU-1。
另有一种解法是 Two Queues, 也是两个队列 不同之处在于,Two Queues 的队列一个是缓存队列,一个是FIFO队列, 当新元素进来的时候,首先进入 FIFO 队列,当该队列中的元素被访问的时候,会进入 LRU 队列(类似用 FIFO 队列做了一个缓冲?)
最近最少使用: 如果一个数据在最近一段时间内使用次数很少,使用频率最低,那么在将来一段时间内被使用的可能性也很小。
与LRU的区别在于LRU是以时间先后来衡量,LFU是以时间段内的使用次数衡量
如上图, LFU 增加了元素的访问频次记录, 访问频次高的元素在队列中的位置就优先, 插入新元素的时候淘汰时间窗口内访问频次最低的元素
优点:
缺点:
LFU 按照访问次数或者访问频率取胜,这个次数有一个累计的长周期, 导致前期经常访问的数据,访问次数很大,或者说权重很高
新来的缓存数据, 哪怕他是突发热点,但是,新数据的访问次数累计的时间太短, 在旧数据面前, 频次仍然很低
所以,LFU 算法中,老的记录已经占用了缓存,过去的一些大量被访问的记录,在将来不一定会继续是热点数据,但是就一直把“坑”占着了,而那些偶然的突破热点数据,不太可能会被保留下来,而是被淘汰。
所以,在存在突发性的稀疏流量下,LFU中的偶然的、稀疏的突发流量在访问频率上,不占优势,很容易被淘汰,造成缓存污染和未来缓存命中率下降。
上面说到了 LFU 的三个缺点: 额外空间、额外开销、无法应对突发稀疏流量
tinyLFU 就是解决这个问题
减少访问频率保存带来的额外空间开销以及减少访问记录更新带来的时间开销, 使用的是 Count–Min Sketch 算法: Count-Min Sketch算法,可以看作是布隆过滤器的同源的算法, 假如我们用一个 hashmap 来存储每个元素的访问次数,那这个量级是比较大的,并且hash冲突的时候需要做一定处理,否则数据会产生很大的误差, 如果用hashmap的方式,相同的下标变成链表,这种方式会占用很大的内存,而且速度也不是很快。 其实一个hash函数会冲突是比较低的,布隆过滤器 的优化之一,设置多个hash函数,多个hash函数,个个都冲突的概率就微乎其微了。 Count-Min Sketch算法将一个hash操作,扩增为多个hash,这样原来hash冲突的概率就降低了几个等级,且当多个hash取得数据的时候,取最低值,也就是Count Min的含义所在。