2

解决缓存穿透的几种应用思考

 2 years ago
source link: https://www.cnblogs.com/niejunlei/p/14965620.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

其实在之前的文章【缓存,确实很香,却也很受伤!】中,对缓存穿透的引发缘由及应对策略做过简要的描述。这篇文章将对这个问题再做下额外的扩展。

一、关于布隆过滤器

布隆过滤器支持两种操作:1、添加元素;2、判断元素是否存在。

布隆过滤器的特性:占用少量内存过滤海量数据

判断元素存在会返回两种结果:

1、不存在:表示元素肯定不存在于布隆过滤器中。

2、存在:表示元素可能存在于布隆过滤器中。说可能,是因为这里有个误差,和布隆过滤器的容量及应用的算法有关。

所以是要判断存在还是判断不存在,要结合实际应用中,能否接受误差,及能够接受多大的误差来决定。

二、新闻过滤

你有一个业务爬虫,不断的爬取其它新闻网站的文章,然后发布到自己的网站,控制重复文章的发布就变得必不可少,海量的新闻信息,怎么过滤?

基于库?库负担大,效率太低;

基于缓存,存储已发布的所有新闻到缓存?成本太高,得不偿失。

新闻有时效价值,历史新闻价值极低,显然基于布隆过滤器的过滤是一个很好的选择。

三、关系过滤

假如我们有一种业务场景,查询两者之间的某种关系数据,比如,a是b是什么关系。

同学?老乡?生意伙伴?

可能只是同学,或者同学+老乡,或者同学+老乡+生意伙伴,又或者什么关系都没有。

我们有数据库存储,也构建了相应的缓存数据来加速响应。

但是当关系数据很少,基数很大时,已有的关系数据相对于所有的穷举关系就变得很渺小,

这时候,缓存所起到的所用就会变得很小,大部分时间不可避免的会产生穿透,进而进行入库查询,那这种问题怎么解决呢?

1、控制查库的时机

a)缓存状态:

基于开篇提到的我们之前文章,我们知道,可以把每次缓存穿透入库查询不存在的数据存储一份状态到缓存,这样下次在查询的时候就可以直接在缓存层面拦截。

这是一个正常的解决方法,对于很多应用场景都很适用。但是对于我们上述列举的情景,这种方法会带来一些不可避免的隐患。

关系是一种两者之间的关联,上面我们讲到过,穷举的关系数据相对于基数对象是一种量级的膨胀,所以以缓存不存在状态来避免穿透会是一种成本无法控制的方式。

改进:虽然这种方式面对的是一个不确定量的缓存需求,但是我们可以通过对状态设置较短的过期时间来一定程度的进行规避。

b)基于过滤器:

开篇介绍过布隆过滤器的特性,我们可以将每次查询不存在的查询放入过滤器,对于过滤器过滤判定结果,不存在的则进一步直接入库查询,判定存在的,则走常规的逻辑,查缓存,查库。

说到这里,可能有人会有疑问,对于已判定不存在关系放入过滤器的,之后业务中产生了关系的,难道还要每次都直接入库查询吗?

,确实是这种方式埋下的一个问题,但是相对于未应用过滤器之前,这种对于业务结果的正确性是没有影响的,只是减慢了一部分查询的响应时间。布隆过滤器的实现对删除元素不太支持,虽然有些衍生的版本实现了这一功能,这里不再讨论。对于上述这种情景,我们可以结合实际的业务数据测量,来定期的重建布隆过滤器来解决。

2、移除入库查询

其实,对于我们上述的这种缓存应用场景,我们为什么要入库查询呢?

因为缓存过期

按照我们通常的认知,缓存好像都必须要有过期时间,一来是为了保障数据一致性(缓存数据与库数据),同时也是为了有效的控制缓存成本(热点数据存留)。

但是,像我们列举的这种量比不均的场景,就不可避免的引发穿透这种更加具有危害性的结果。

因此,此处,我们需要换一种方式来应用缓存。

我们可以将已有的关系数据全部缓存起来,设置不过期,同时移除后续的入库查询逻辑,所有的查询全部由缓存数据来响应。

同时为了保障数据一致性,我们可以对数据变更做补偿措施

数据的变更毕竟很少,相对于查询毕竟有量级的差距。

因此,每次数据变更我们都可以进行一次数据同步(缓存和数据库),当然,方式可以选择同步或者异步


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK