高可用服务设计之如何应对缓存穿透

Posted 算法和技术SHARING

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高可用服务设计之如何应对缓存穿透相关的知识,希望对你有一定的参考价值。

背景

用户中心是授权逻辑与用户信息相关逻辑构建的应用。分布式系统中,大多数业务都需要和用户中心打交道,为了保证用户中心服务的高可用,避免不了做缓存、导入搜索引擎从而降低数据库的压力。然而有些不经过用户中心授权的业务场景查询用户中心的数据,可能引发大量无效的查询,发生缓存穿透,直接对搜索引擎和数据库造成压力。那么如何解决用户中心缓存穿透的问题呢?要想解决缓存穿透,我们首先要了解一下,什么是缓存穿透。

缓存穿透

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案

缓存穿透其实有很多种解决方案,下面简单介绍两种。

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。 

缓存空对象实现起来非常简单,但是会存在两个问题:

1、占用更多的存储空间:如果空值需要被缓存起来,这就意味着缓存集群需要更多的空间存储更多的键,这当中会有很多的空值的键。

2、数据一致性:如果单纯只是对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。因此需要在存储层数据有任何变更的同时,更新/删除缓存层的数据,尽量保证缓存层和存储层的数据一致性。

那么有没有更好的解决方案呢,答案是当然有啦,接下来就着重说一下布隆过滤器是怎么“隔档”无效查询,解决缓存穿透的!

布隆过滤器

1、基本概念

(1) 布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。

(2) 布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

2、特点

  • 空间效率高和查询效率高的概率型数据结构。

  • 对于一个元素检测是否存在的调用,BloomFilter会告诉调用者两个结果之一:可能存在或者一定不存在。

  • 一个很长的二进制向量 (位数组)。

  • 一系列随机函数(哈希)。

  • 有一定的误判率(哈希表是精确匹配)。

3、原理

布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k。 

高可用服务设计之如何应对缓存穿透

(1) 添加元素过程

  • 将要添加的元素给k个哈希函数。

  • 得到对应于位数组上的k个位置。

  • 将这k个位置设为1。

(2) 查询元素过程

  • 将要查询的元素给k个哈希函数。

  • 得到对应于位数组上的k个位置。

  • 如果k个位置有一个为0,则肯定不在集合中。

  • 如果k个位置全部为1,则可能在集合中。

4、相关公式

很显然,根据布隆过滤器的原理和特性,bit数组大小和哈希函数的个数都会影响误判率。那么布隆过滤器是如何权衡bit数组大小和哈希函数个数的呢?

假设布隆过滤器bit数组大小为m,样本数量为n,失误率为p。

高可用服务设计之如何应对缓存穿透

假设样本容量n=5000W,误判率是0.03,那么所需要的内存空间大小是

m = -5000W * -3.057 / (0.7)^2 ≈ 318,437,500 ≈ 39.8MB

5、演示

https://www.jasondavies.com/bloomfilter

(2)可能存在

高可用服务设计之如何应对缓存穿透

(3)一定不存在

高可用服务设计之如何应对缓存穿透

6、应用场景

  • 网页爬虫对URL的去重。

  • 黑名单,垃圾邮件过滤。

  • 解决数据库缓存穿透。

实际应用
微门户侧

1、微信小程序用户信息查询服务每天过滤无效请求达150W+次,过滤器拦截成功率在80%左右。

从数据上可以看出效果非常明显,很优雅的解决了缓存穿透的问题。微门户系统中主要是根据手机号查询用户信息存在很多无效的请求,布隆过滤器可以针对不存在的手机号和不合法的手机号进行拦截。布隆过滤器内存占用较小,执行效率够快,从空间和时间的维度上为核心业务系统提供自我保护能力。

技术支持

1、单机版布隆过滤器

Guava已经实现了单机版的布隆过滤器,既然是单机版,肯定是直接操作当前系统JVM内存。这种过滤器无法应用的微门户现有的系统中去。

2、分布式布隆过滤器

动手实现基于Redis的分布式布隆过滤器。通过对guava bloom filter的分析,由单机版改造成分布式版,只需要重新实现三个guava bloom filter的三个类(BloomFilter,BloomFilterStrategies,BitArray)。RedisBitArray改造不是很麻烦,只需要引入操作分布式缓存的JedisCluster对象就好了。get和set操作对应JedisCluster对象的getbit和setbit操作(针对String类型的值,Redis通过 位操作 实现了BitMap数据结构)。

BloomFilter和BloomFilterStrategies的改造相对比较简单,这里就不详细说明了。

3、路由布隆过滤器

为什么要有路由布隆过滤器?通过上面的公式可以知道,当要插入的样本数量n越大,那么需要分配的内存容量m也会越大。也就是布隆过滤器的不当使用极易产生大 Value,增加 内存溢出或者阻塞风险,因此生成环境中建议对体积庞大的布隆过滤器进行拆分,拆分的规则我们定义为按照一定的路由规则对应到不同的布隆过滤器。

(1) 设计方案

高可用服务设计之如何应对缓存穿透

(2) 路由策略

  • routing方法根据样本计算出路由key值。

  • exceptedInsertions方法根据样本获取到路由key值,然后计算期望插入的样本数量。

4、布隆过滤器使用

为了方便理解,布隆过滤器可以简化成两个操作。

  • put操作:将样本放入到布隆过滤器。

  • mightContain操作:判断布隆过滤器中是否可能存在样本。

  1. update业务将增量的样本(手机号)同步到(put操作)布隆过滤器。

  2. 全部存量的样本(手机号)初始化到(put操作)布隆过滤器。微门户系统的用户信息在ES有存储,通过slice scroll技术,可以在6个小时左右把亿级数据遍历完并初始化到布隆过滤器里。

  3. search业务根据样本(手机号)判断布隆过滤器不存在(!mightContain操作)该样本,拦截成功,直接返回。

注:上面的三个步骤一定要按照顺序执行呦!

总结

下图是各渠道按手机号查询用户信息服务集成布隆过滤器后查询量(请求总量)和拦截量(无效请求量)对比。

此次服务优化可有效拦截不存在的或者不合法的手机号的相关查询,防止缓存穿透。

在使用布隆过滤器需要注意的是,需要考虑样本容量大小、使用规范、路由规则(请仔细阅读实际应用#技术支持相关内容)。

再强调一下布隆过滤器的优缺点。优点是空间效率和查询时间都比一般的算法要好的多;缺点是有一定的误识别率和删除困难。虽然布隆过滤器判断样本包含在集合中时有误判率,但是如果布隆过滤器告诉你这个样本不包含在集合中,那肯定就不在。



以上是关于高可用服务设计之如何应对缓存穿透的主要内容,如果未能解决你的问题,请参考以下文章

如何应对缓存穿透和缓存雪崩问题?讲的明明白白!

掌握之分布式-4.缓存

Redis缓存,持久化,高可用

redis缓存穿透,频繁查询db,怎么解决

Redis 缓存雪崩缓存击穿缓存穿透原因,解决方案?

RedisRedis缓存穿透和雪崩