PHP 详细面试总结 (四 Redis 缓存穿透)

缓存穿透

  • 缓存系统,按照 KEY 去查询 VALUE ,当 KEY 对应的 VALUE 一定不存在的时候并对 KEY 并发请求量很大的时候,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
  • 比如文章表,查询一个不存在的 id ,每次都会访问 DB ,如果有人恶意破坏,很可能直接对 DB 造成影响。也会对后端造成很大的压力。

解决方法:

1.缓存层缓存空值。

  • –缓存太多空值,占用更多空间。(优化:给个空值过期时间
  • –存储层更新代码了,缓存层还是空值。(优化:后台设置时主动删除空值,并缓存把值进去

2.使用布隆过滤器。

  • 对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。
  • 基本原理及要点:对于原理来说很简单,位数组 +k 个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有 hash 函数对应位都是 1 说明存在,很明显这个过程并不保证查找的结果是100%正确的。同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字。所以一个简单的改进就是 counting Bloom filter ,用一个 counter 数组代替位数组,就可以支持删除了。添加时增加计数器,删除时减少计数器

3.另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

4.后台设置定时任务,主动地去更新缓存数据。这种方案容易理解,但是当Key比较分散的时候,操作起来还是比较复杂的。

5.分级缓存。比如设置两层缓存保护层,1级缓存失效时间短,2级缓存失效时间长。有请求过来优先从1级缓存中去查找,如果在1级缓存中没有找到相应数据,则对该线程进行加锁,这个线程再从数据库中取到数据,更新至1级和2级缓存。其他线程则直接从2级线程中获取。

6.提供一个拦截机制,内部维护一系列合法的Key值。当请求的Key不合法时,直接返回。

备注:

  • 比如数据库中有 10000 个条件,那么布隆过滤器的容量size设置的要稍微比 10000 大一些,比如 12000 .
  • 对于误判率的设置,根据实际项目,以及硬件设施来具体决定。但是一定不能设置为0,并且误判率设置的越小,哈希函数跟数组长度都会更多跟更长,那么对硬件,内存中间的要求就会相应的高。
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001); 
  • 有了 size 跟误判率,那么布隆过滤器就会产生相应的哈希函数跟数组。
  • 综上:我们可以利用布隆过滤器,将 redis 缓存击穿控制在一个可容忍的范围内。

参考文章:https://blog.csdn.net/lby0307/article/deta...

缓存雪崩

问题

分布式系统都存在这样一个问题,由于网络的不稳定性,决定了任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩连锁效应。

缓存雪崩

缓存雪崩就是指缓存由于某些原因导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃、发生灾难,也就是上面提到的缓存击穿。

几种场景

  • 流量激增:比如异常流量、用户重试导致系统负载升高;
  • 缓存刷新:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;
  • 程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;
  • 硬件故障:比如宕机,机房断电,光纤被挖断等。
  • 数据库严重瓶颈,比如:长事务、sql超时等。
  • 线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;

eg:

在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

解决方法

加锁排队. 限流-- 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token

  • Bucket 4.漏桶 leaky bucket [1]
  • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex
  • key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
  • SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

数据预热

可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

做二级缓存,或者双缓存策略。

A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

缓存永远不过期

这里的“永远不过期”包含两层意思:

(1) 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期.

从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

参考链接: https://blog.csdn.net/fei33423/article/det...

缓存预热

思路

a.提前给redis中嵌入部分数据,再提供服务

b.肯定不可能将所有数据都写入redis,因为数据量太大了,第一耗费的时间太长了,第二redis根本就容纳不下所有的数据

c.需要更具当天的具体访问情况,试试统计出频率较高的热数据

d.然后将访问频率较高的热数据写入到redis,肯定是热数据也比较多,我们也得多个服务并行的读取数据去写,并行的分布式的缓存预热

e.然后将嵌入的热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库奔溃了

解决思路:

1、直接写个缓存刷新页面,上线时手工操作下;

2、数据量不大,可以在项目启动的时候自动进行加载;

3、定时刷新缓存;

最后

Redis的性能极高,读的速度是110000次/s,写的速度是81000次/s,支持事务,支持备份,丰富的数据类型。

任何事情都是两面性,Redis也是有缺点的:

1、由于是内存数据库,所以单台机器存储的数据量是有限的,需要开发者提前预估,需要及时删除不需要的数据。
2、当修改Redis的数据之后需要将持久化到硬盘的数据重新加入到内容中,时间比较久,这个时候Redis是无法正常运行的。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!