引言

上一篇文章中,我们介绍了常见的缓存架构

常见缓存架构 -- 穿透型缓存与旁路型缓存

 

缓存对于查询压力很大的服务来说是必不可少的解决方案,对于访问频率极高及读多写少的业务来说,使用缓存提升服务性能,减轻后端服务器压力等方面有着很好的效果

但是,没有任何一种技术方案是只有好处没有弊端或风险的,本文我们就来详细介绍一下在缓存使用过程中可能带来的风险与解决办法

《架构师36项修炼》缓存使用中存在的风险及应对

 

 

缓存雪崩

在服务代码编写过程中,最应警惕的就是雪崩效应的发生,在缓存的使用过程中,缓存雪崩的问题也需要提前考虑和处理

缓存雪崩指的多个缓存中的数据同时失效,此时新的数据请求全部 MISS,造成后端服务压力突增的现象,此时巨大的压力可能造成服务响应延迟甚至后端服务器崩溃

除此之外,当缓存机器宕机,对于通常使用的旁路型缓存架构,所有请求都会直接请求道后端服务器,从而造成雪崩效应

 

 

解决方案

 

  1. 在数据放入缓存时,对于不同的数据设置不同的过期时间,让缓存的失效时间点尽量均匀,避免集中失效
  2. 缓存失效后,通过加锁或消息队列异步方式减轻后端服务器压力或在 nginx 层限制相同数据单位时间内单个 ip 的访问次数,让到达后端服务器的请求流量尽量可控
  3. 缓存多集群,一旦某个缓存集群出现宕机,自动化运维工具可以自动将缓存服务切换到备用集群,从而避免后端服务器流量的激增
  4. 最为重要的,仍然是服务上线前各节点的压测,以及各环节异常情况发生时的自动降级,切忌心存侥幸

 

缓存击穿

缓存击穿问题出现在缓存中存在某个极为热点的数据,一旦该数据过期,大量请求立即穿透到后端服务器,造成后端服务器压力的激增甚至宕机

相比于缓存穿透,缓存击穿有时更加难以发现,表现为缓存运行情况一切正常,后端服务器请求量突然出现峰值,如果不加关注可能就会忽略掉缓存击穿问题的存在,从而埋藏下十分危险的安全隐患

 

解决方案

 

  1. 首先,完备的监控和适时的压测是必不可少的,在大流量到来前提前发现、提前应对,关注监控哪怕是一个峰值出现的不起眼异常,并且找到原因
  2. 从根本上,只要后台定时检查缓存中数据的失效时间,在失效前顺延缓存数据的过期时间,让缓存永不过期就不会出现缓存击穿的风险
  3. 上面我们在缓存雪崩的应对方案中提到的分级缓存、多缓存集群、后端服务器通过加锁、消息队列异步、限流等方式让请求流量尽量可用的方案依然可用于解决缓存击穿问题

 

缓存穿透

缓存穿透是缓存使用中十分常见的一个问题,也是恶意攻击的一个常见手段

无论是穿透型缓存还是旁路型缓存,只要缓存中不存在被请求数据,都会到后端服务器尝试获取

那么,只要通过构造一定不存在的数据,大量请求服务器,这些请求流量就会直接压到后端服务器,进而影响甚至压垮服务器

 

解决方案

 

  1. 数据校验,例如我们所有数据都是六位数字,那么不满足这个条件的数据应该直接被校验过滤,而不需要去请求缓存或数据库
  2. 缓存空数据,将返回结果为空的数据进行短时间缓存是解决缓存穿透问题最简单暴力的办法了,这样接下来相同的空数据将会被缓存直接拦截,但实际业务中,空数据很可能是来自于外部攻击,而这样的攻击通常不会一直使用相同的数据访问,采用这个方法反而可能造成缓存暴涨,引起新的问题
  3. 布隆过滤器,布隆过滤器是解决缓存穿透最常用的

 

缓存穿透的解决 -- 布隆过滤器

布隆过滤器本质上是一种设计巧妙的概率型数据结构,通过高效的查询,能够快速告诉你某条数据一定不存在还是可能存在,因为他占用空间小、查询速度快等优势被广泛使用

 

设计原理

 

如果能够将后端数据库中所有数据都载入到缓存中,就不会发生缓存穿透问题了,因为此时一旦在缓存中没有查找到数据,就说明后端数据库中也并不存在该数据,就没有必要穿透到后端数据库再次访问了

问题在于缓存的内存空间有限,无法将所有数据载入到缓存中,只能按照我们的策略缓存部分热点数据

但是我们沿着这条思路继续思考,如果不缓存全部数据,而是改为缓存全部数据的 hash 值,就可以大幅缩小数据占用的缓存空间了,虽然这样我们没办法确认在缓存中已存在 hash 值的数据在后端数据库是否真实存在,因为不同的数据 hash 值可能相同,但对于不存在 hash 值的数据,我们就不再需要访问后端数据库了,从而很大程度上可以避免缓存穿透问题

 

布隆过滤器的实现

 

我们可以在缓存中通过一个 bit 数组来实现,这与 bitmap 算法如出一辙

《架构师36项修炼》缓存使用中存在的风险及应对

 

此时我们通过三种 hash 算法分别计算数据的 hash 值,例如我们的数据是 "baidu",计算出来的 hash 值分别是 1、4、7,于是我们将对应的位置位

《架构师36项修炼》缓存使用中存在的风险及应对

 

接下来,我们再插入一条新的数据 “tencent”,hash 值分别是 3、4、8,于是我们将对应的位置位:

《架构师36项修炼》缓存使用中存在的风险及应对

 

那么,这时请求数据 "alibaba",假设这条数据通过三种 hash 算法计算出的值为 1、2、8,我们看到,在 bit 数组中,2 没有被置位,于是我们可以立即判断出 alibaba 并不在数据库中

而如果请求的数据通过三种 hash 算法计算出的值为 1、3、8,由于这三个位置都已经被置位,那么这个数据可能存在也可能不存在

 

 

布隆过滤器长度的取值

 

下面是一个布隆过滤器误报率的统计图:

《架构师36项修炼》缓存使用中存在的风险及应对

 

图中,k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率

可以推导出下面的公式:

《架构师36项修炼》缓存使用中存在的风险及应对

 

布隆过滤器的优缺点

 

布隆过滤器使用相对很小的内存开销,通过 bitmap 算法实现了一个概率模型,以一定概率对不存在数据的请求可以在第一时间返回不存在,从而避免了缓存穿透的风险

其优点是显而易见的:

 

  1. 占用内存小
  2. 执行速度快

 

但其缺点也同样明显:

 

  1. 概率模型,不能 100% 确定数据是否一定存在或不存在
  2. 扩容困难,如果由于数据量的大幅增长,我们需要让布隆过滤器的长度扩展一倍,我们需要选定新的 hash 算法或参数重新对数据库中所有数据生成新的 bitmap
  3. 查询性能损耗依赖于 hash 算法,因此性能很低的哈希函数不是个好选择,推荐 MurmurHash、Fnv 等高性能 hash 算法
  4. 无法实现数据删除,假设我们要删除一个数据,我们并不知道他通过 hash 算法得到的结果与其他数据的结果是否有重合,因此不能轻易去复位 bitmap 中的值,这对这个问题,解决方案是将 bitmap 改为 hash,value 设为该 hash 值对应的数据数量,但这样会导致布隆过滤器占用的空间大幅上升,有些得不偿失

相关文章: