跬步贴|5 分钟搞定缓存击穿问题
应用中使用缓存来减轻对低速存储器的读请求压力,几乎已成为互联网应用标配。在本篇文章中,笔者将通过对互联网应用中常见的缓存击穿场景解决方案进行阐述。
01 / 场景简述
在本文中所解决缓存击穿问题主要面向如下数据缓存场景:
缓存数据加载采用懒加载模式(即极少比例热数据提前加载至缓存)
缓存存在形态为单机进程内缓存或者集中式进程外缓存
缓存中所最终存储的数据为热数据,且热数据占比相较于数据总量极低
应用于读写比极高的应用场景
由上述场景描述可知,此类缓存方案主要用以解决在高读写比应用场景下的缓存耗损最高性价比的问题,其解决方案可简化为下图:
基于上图,此方案特征如下:
未被加载的数据存储于低速缓存器中(如传统关系型数据库、文件系统等)
数据只有在被使用到时,才会被加载至缓存
若数据在缓存中未被命中,则从低速存储器中查询后加载至缓存中
基于上述方案特征及前章节所描述的场景特征可知,此方案可最大限度地提高缓存使用率。而该方案同样也具有不可忽视的缓存击穿问题,即当大量查询请求数据在缓存中未命中,请求透传至低速存储器,极有可能会导致低速存储器处理超时,甚至宕机的风险。
而此类场景的发生可能是来自外部的恶意攻击,亦可能是正常的业务请求。
02 / 解决方案
缓存击穿的解决方案有两类:提升缓存命中率(事前)、低速存储器防护(事后),在下面的章节,笔者将基于此二类解决方案进行详细讲解
2.1 提升缓存命中率
提升缓存命中率的关键在于防患于未然,最大限度地将数据请求拦截在低速存储器之前,方案细分为如下几点,详解如下:
2.1.1 校验前置
在查询缓存之前,对待查询的数据进行校验,拦截非法数据查询请求。以订单详情查询为例,订单编号作为缓存数据主键,其最简单的校验规则可以为:订单编号前五位必须为"ORDNO"及订单编号长度必须为 37 位。当请求的订单编号无法通过校验时,数据查询请求被拦截。
关于校验规则一个要点是:校验规则的复杂性与缓存命中率成正比,另外一个要点在于,基于高性能的要求,校验规则可以复杂,但是必须保持高性能,否则使用缓存的优势便消失殆尽,可以使用类似于位移操作等极为复杂但高性能的校验方法来进行校验。
2.1.2 空值防范
数据在缓存中未命中,在低速存储器也无法查询到数据,若大量重复查询该数据,将给低速存储器带来极大的查询压力。此类场景可以通过将查询所得的空值以特定值(必须确保该特定值与正常值不可能相同)存储于缓存中,并辅之以缓存的失效机制,可确保此类数据在一定的单位时间内,查询请求只会透传至低速存储器中 N 次(单机进程内缓存,且集群规模为 N 个节点)。
2.1.3 数据预载
在某种场景下,合法的数据查询请求在瞬间大量涌入,虽然实施了校验前置及空值防范,依旧无法解决此类场景。而此类场景可以通过对既往冷热数据的分析,在此类场景发生前,提前将热数据加载至缓存中,这对于冷热数据的分布分析要求较高,但也最为有效。
2.2 低速存储器防护
在提升缓存命中率章节中,我们所解决的是降低透传至低速存储器的恶意/非法请求规模,而在本章节,我们所解决的是当超过低速存储器处理能力的合法数据查询请求涌入时,如何最大限度地进行防护,方案亦分为如下几点,详解如下。
2.2.1 过载防护
过载防护是指将超过低速存储器处理能力的请求进行提前拦截,方案实施要点如下:
低速存储器单位时间处理能力上限评估,一般可以通过 QPS 来进行指征,这是一个实验值,可通过性能测试获得
对当前低速存储器在单位时间内已处理请求计数,即当前每秒/每分钟已处理了多少请求
防护策略的执行,当前单位时间处理请求大于单位时间内处理请求上限时,则对该请求进行拦截
基于上述要点可知,第二点为关键,即如何记录低速存储器单位时间内已处理的请求数,尤其在集群环境下,变得更为复杂,可以通过集中式的高速计数器(如 Redis 的自增机制)来执行,这是一种精准但昂贵的解决方式,更为低廉的方案可以将低速存储器的最高处理能力/N(N 为集群节点数)作为单机的低速存储器处理能力上限,在内存中进行计数操作。
2.2.2 熔断机制
熔断机制的实施以过载防护实施为前提,基于防护机制,超过低速存储器处理能力的请求被多次拦截之后,在数据请求接入层可以基于拦截响应进行判定是否在上层对请求进行拦截,而无需将超过处理能力的请求一直传递至低速存储器。
03 / 方案总结
版权声明: 本文为 InfoQ 作者【架构师跬步营】的原创文章。
原文链接:【http://xie.infoq.cn/article/8fac35f887d1bde51459ed61a】。文章转载请联系作者。
评论