程序员社区

缓存三大问题解决方案


title: 缓存三大问题解决方案
date: 2021/05/25 10:57


缓存穿透

缓存穿透指的是查询一个一定不存在的数据,由于存储层在查不到数据时不写入缓存,这将导致这个不存在的数据每次请求都要到存储层查询,从而失去了缓存的意义。

如果有人利用不存在的key频繁攻击我们的应用,可能 DB 就挂掉了。

解决方案

  1. 布隆过滤器,将所有可能存在的数据 hash 到一个足够大的 bitmap 中,一个一定不存在的数据则会被拦截掉,从而避免了对底层存储系统的查询压力。

因为 BloomFilter 如果声称其中包含元素,则可能是错误的,但是如果声明其中不包含元素,则肯定是正确的。

  1. 空值缓存:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟,这样则可以应对短时间的大量的该key攻击,设置为较短的失效时间是因为该值可能业务无关,存在意义不大,且该次的查询也未必是攻击者发起,无过久存储的必要,故可以早点失效。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

另一种情况是缓存服务器宕机,也会造成雪崩现象。

解决方案

  1. 永不过期:简单粗暴

  2. 线程互斥:通过分布式锁只允许一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据才可以,每个时刻只有一个线程在执行请求,减轻了db的压力,但缺点也很明显,降低了系统的qps。

  3. 交错失效时间:这种方法时间比较简单粗暴,既然在同一时间失效会造成请求过多雪崩,那我们错开不同的失效时间即可从一定长度上避免这种问题,在缓存进行失效时间设置的时候,从某个适当的值域中随机一个时间作为失效时间即可。

  4. 请求限流:

    • 限制请求数量,常见的限流算法有滑动窗口,令牌桶算法和漏桶算法,或者直接使用队列、加锁(信号量)等
    • 限制数据库的每秒请求数,避免数据库挂掉。对于被限流的请求,采用服务降级处理,比如提供默认的值,或者空白值
  5. 本地缓存:如果使用本地缓存,即使分布式缓存挂了,也可以将数据库查询的结果缓存到本地,避免后续请求全部达到数据库中

  6. 缓存高可用:使用Redis Sentinel等搭建缓存的高可用,避免缓存挂掉无法提供服务的情况,从而降低出现缓存雪崩的情况

缓存击穿

缓存击穿实际上是缓存雪崩的一个特例,对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,由于系统中对这些热点的数据缓存也存在失效时间,在热点的缓存到达失效时间时,此时可能依然会有大量的请求到达系统,没有了缓存层的保护,这些请求同样的会到达db从而可能引起故障。击穿与雪崩的区别即在于击穿是对于特定的热点数据来说,而雪崩是全部数据。

解决方案

  1. 永不过期:简单粗暴
  2. 请求限流:
    • 限制请求数量,常见的限流算法有滑动窗口,令牌桶算法和漏桶算法,或者直接使用队列、加锁(信号量)等
    • 限制数据库的每秒请求数,避免数据库挂掉。对于被限流的请求,采用服务降级处理,比如提供默认的值,或者空白值
  3. 本地缓存:使用 LRU 做二级缓存

扩展 - 服务雪崩

在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。

由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩效应” 。

缓存三大问题解决方案插图

雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题,不会影响到其它服务的正常运行。也就是"雪落而不雪崩"。

5.2 常见的容错方案

容错说白了就是保护自己不被猪队友拖垮的一些措施,下面介绍常见的服务容错思路和组件。

常见的容错思路

1、隔离

它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。常见的隔离方式有:线程池隔离和信号量隔离。

缓存三大问题解决方案插图1

Hystrix线程隔离技术解析-线程池

Hystrix线程隔离技术解析-信号量

2、超时

在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。

缓存三大问题解决方案插图2
3、限流

限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。

缓存三大问题解决方案插图3
4、熔断

在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整 体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

缓存三大问题解决方案插图4

服务熔断一般有三种状态:

  • 熔断关闭状态(Closed) :服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
  • 熔断开启状态(Open) :后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
  • 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
5、降级

降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。

缓存三大问题解决方案插图5

常见的容错组件

Hystrix:

Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。

Resilience4J:

Resilicence4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替代产品。不仅如此,Resilicence4j还原生支持Spring Boot 1.x/2.x,而且监控也支持和 prometheus等多款主流产品进行整合。

Sentinel:

Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数等 异常比率模式、超时熔断 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种配置源 支持多种数据源 有限支持
扩展性 丰富的 SPI 扩展接口 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
集群流量控制 支持 不支持 不支持
流量整形 支持预热模式、匀速排队模式等多种复杂场景 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 不提供控制台,可对接其它监控系统
多语言支持 Java / C++ Java Java
开源社区状态 活跃 停止维护 较活跃

参考文章

缓存三大问题及解决方案

缓存穿透,缓存击穿,缓存雪崩解决方案分析

*缓存失效的场景

赞(0) 打赏
未经允许不得转载:IDEA激活码 » 缓存三大问题解决方案

一个分享Java & Python知识的社区