posts

May 22, 2019

缓存穿透、缓存击穿与缓存雪崩

Estimated Reading Time: 1 minutes (90 words)

翻译自3 major problems and solutions in the cache world

当前的IO设备远远不能满足互联网应用程序的大量读写请求。然后有一个缓存,使用内存的高速读写性能来应对大量的查询请求。但是,内存资源非常宝贵,将全部数据存储在内存中显然是不切实际的。因此,当前内存和IO的组合,内存只存储热点数据,而IO设备存储全部数据。缓存的设计包含很多技巧,不正确的设计会导致严重的后果

1 缓存穿透(Cache penetration)

在大多数互联网应用程序中:

  1. 当业务系统发起某个查询请求时,它首先确定数据是否存在于缓存中;
  2. 如果有缓存,则直接返回数据;
  3. 如果缓存不存在,请再次查询数据库并返回数据。

在理解了上述过程之后,我们来谈谈缓存穿透

1.1 是什么

当业务系统发起查询时,根据上面的过程,查询将首先进入缓存,因为缓存不存在,然后转到数据库进行查询。由于数据根本不存在,因此数据库也返回**null**。这是缓存穿透。

总结一下:业务系统访问根本不存在的数据称为缓存穿透。

1.2 危险是什么

如果查询请求中不存在大量数据,那么这些大量请求将落入数据库,数据库压力将急剧增加,这可能导致系统崩溃。 (你必须知道当前业务系统中最脆弱的是IO,有点它会在压力下崩溃,所以我们必须想办法保护它)。

1.3 为什么会发生

  1. 恶意攻击故意创建大量不存在的数据来请求我们的服务。由于缓存中不存在这些数据,因此大量请求会进入数据库,这可能导致数据库崩溃。
  2. 代码逻辑错误。这是程序员的锅,无话可说,必须在开发中避免!

1.4 如何避免

以下是防止的两种方法:

1.4.1 缓存空数据

缓存穿透的原因是缓存中没有用于存储这些空数据的密钥,导致所有这些请求都到达数据库。

然后,我们可以稍微修改业务系统的代码,并将具有空数据库查询结果的密钥存储在缓存中。当再次发生对密钥的查询请求时,缓存直接返回**null**而不查询数据库。

1.4.2 布隆过滤器(BloomFilter)

它需要在缓存之前添加一个屏障,它存储当前数据库中存在的所有key

当业务系统有查询请求时,首先转到BloomFilter以检查key是否存在

1.4.3 比较两种方案

这两种解决方案都可以解决,但使用场景不同。

对于某些恶意攻击,查询的key通常不同,而数据窃贼更多。此时,第一个方案太多了。因为它需要存储所有空数据的key,并且这些恶意攻击的密钥通常是不同的,并且相同的密钥通常只被请求一次。因此,即使缓存了这些空数据的密钥,由于不再使用第二次,因此无法实现保护数据库的作用。

因此,对于空数据的key不同并且key重复请求的概率的情况,应该选择第二方案。对于空数据的key数量有限且密钥重复请求的概率很的情况,应选择第一种方案。

2 缓存击穿(Cache breakdown)

又称为热点数据集失效(Hotspot data set is invalid)

2.1 是什么

我们通常为缓存设置一个到期时间。到期时间后,数据库将被缓存直接删除,从而在一定程度上保证数据的实时性

但是,对于一些请求非常高的热点数据(hotspot data),一旦有效时间过去,此时将有大量请求落在数据库上,这可能导致数据库崩溃。过程如下:

如果热点数据失败,那么当再次有数据查询请求**[req-1]时,它将转到数据库查询。但是,从请求发送到数据库到数据更新到缓存的时间,由于数据仍然不在缓存中**,因此在此期间到达的查询请求将落在数据库上,这将导致数据库巨大的压力。此外,当完成这些请求查询时,重复更新缓存。

2.2 如何避免

2.2.1 互斥(Mutex)

我们可以使用缓存附带的锁定机制。启动第一个数据库查询请求时,将锁定缓存中的数据;此时,到达缓存的其他查询请求将无法查询该字段,因此将被阻塞等待;请求完成数据库查询并缓存数据更新值后,锁定被释放;此时,可以直接从缓存中检索其他阻止的查询请求。

当热点数据失败时,只有第一个数据库查询请求被发送到数据库,并且所有其他查询请求都被阻止,从而保护数据库。但是,由于使用了互斥锁,其他请求将阻止等待,系统吞吐量将下降。这需要与实际的业务考虑相结合才能实现这一点。

互斥锁可以避免因热点数据失败而导致数据库损坏的问题。在实际业务中,通常存在一批热点数据同时失败的场景。那么如何防止这种情况下的数据库超负荷?

2.2.2 设置不同的到期时间

当我们将这些数据存储在缓存中时,我们可以错开缓存到期时间。这可以避免同时发生故障。例如,在基准时间添加/减去随机数以错开这些缓存的到期时间。

3 缓存雪崩(Cahce avalanche)

3.1 是什么

从上面可以看出,缓存实际上起到了保护数据库的作用。它有助于数据库承受大量查询请求,从而避免易受攻击的数据库。

如果由于某种原因缓存出现故障,那么最初被缓存阻止的大量查询请求将像疯狗一样涌向数据库。此时,如果数据库无法承受这种巨大的压力,它就会崩溃。这是缓存雪崩。

3.2 如何避免

3.2.1 缓存集群确保高可用

也就是说,在发生雪崩之前,采取预防措施以防止雪崩的发生。

3.2.2 熔断器

Hystrix是一种开源的“抗雪崩工具”,**降级(downgrade)限流(current limit)**来减少雪崩后的损耗。

Hystrix是一个使用命令模式(command pattern)的Java类库,每个服务处理请求都有自己的处理器。所有请求都通过各自的处理器。处理器记录当前服务的请求失败率。一旦发现当前服务的故障率达到预设值,将拒绝所有后续服务请求并返回默认结果。这就是所谓的保险丝,即熔断器。经过一段时间后,Hystrix将释放部分服务请求,并再次计算其请求失败率。如果请求失败率此时满足预设值,则流量限制开关完全打开;如果请求失败率仍然很高,则拒绝所有服务请求,是为限流。Hystrix将默认结果直接返回给那些被拒绝的请求,是为降级