腾讯面试:怎么解决缓存穿透、击穿和雪崩问题?

362 阅读8分钟

前不久,有位朋友去腾讯面试,他说被问到怎么解决缓存穿透、击穿和雪崩问题? 本文将跟大家一起来探讨如何回答这个问题。

这个问题之所以频繁出现,是因为在使用缓存时,稍有不慎就可能碰到它们。一旦缓存崩溃,大量的请求就会直接转到数据库,造成数据库的巨大压力,甚至可能导致崩溃。然而,许多新手在初次接触缓存时,往往没有意识到这一潜在问题,直到线上故障发生时才开始考虑缓存崩溃的影响。因此,面试官通常会在面试中关注你是否具备解决此类问题的能力。

什么是 缓存雪崩、击穿、穿透?

首先大家得知道缓存雪崩、击穿、穿透是什么意思,并且了解发生这些问题原因,根据对应的原因,解决问题,“对症下药”。

什么是缓存雪崩?

通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库。并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

那么,当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

什么是缓存击穿?

我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。

什么是缓存穿透?

当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。

当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

缓存穿透的发生一般有这两种情况:

  • 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
  • 黑客恶意攻击,故意大量访问某些读取不存在数据的业务;


解决缓存雪崩、击穿、穿透问题

通过上述的内容,大家已经认识缓存雪崩、击穿、穿透问题的概念,了解了这些问题发生的原因,下面就根据这些问题的不同原因,解决问题。

解决缓存雪崩问题

发生缓存雪崩有两个原因:
大量数据同时过期
Redis 故障宕机

不同的诱因,应对的策略也会不同

大量数据同时过期

针对大量数据同时过期而引发的缓存雪崩问题,常见的应对方法有下面这几种:
均匀设置过期时间
互斥锁

均匀设置过期时间

如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。

最简单的思路,就是在过期时间的基础上加一个偏移量。

我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。

例如,如果我预计这一批数据的过期时间是 15 分钟,那么在设置每条数据的过期时间时,我会在 15 分钟的基础上随机加上一个 0 到 180 秒之间的偏移量。这样,数据就不会在同一时刻过期,从而避免了缓存雪崩的问题。

互斥锁

当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。

未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。

Redis 故障宕机

针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法:
服务熔断或请求限流机制

服务熔断或请求限流机制

因为 Redis 故障宕机而导致缓存雪崩问题时,我们可以启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。

服务熔断机制是保护数据库的正常允许,但是暂停了业务应用访问缓存服系统,全部业务都无法正常工作。

为了减少对业务的影响,我们可以启用请求限流机制,只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。

解决缓存击穿问题

解决缓存击穿是很容易的,只需要用到我们在缓存模式里面提到的 singleflight 模式。

也就是说,就算是一个热点数据,当几百个请求缓存未命中的时候,在 singleflight 模式之下,也只有一个请求会真的去查询数据,剩下的都在等着这个请求查询回来的结果。

当然,因为缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集,所以应对缓存击穿可以采取前面说到的方案:

  • 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

解决缓存穿透问题

缓存穿透的发生一般有这两种情况:
业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
黑客恶意攻击,故意大量访问某些读取不存在数据的业务;

应对缓存穿透的方案,常见的方案有三种:
非法请求的限制
缓存空值或者默认值
使用布隆过滤器,快速判断数据是否存在,避免通过查询数据库来判断数据是否存在

非法请求的限制

当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透。

因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。

缓存空值或者默认值

当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。

使用布隆过滤器

我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。

即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

就业陪跑训练营学员投稿

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。