15 | 缓存的使用姿势(三):缓存穿透了怎么办?


前面了解了 缓存,你应该知道了对于缓存来说命中率是它的生命线。

在地缓存命中率的系统中,大量查询商品信息的请求会穿透到数据库,因为数据库对于并发的承受能力是比较脆弱的。一旦数据库承受不了大量刷新的商品页面,查询就会变慢,大量请求就会阻塞在数据库查询书航,造成应用服务器和线程资源占满,最终导致你的电商系统崩溃。

一般来说,我们的核心系统缓存命中率要保持在99%以上,非核心的缓存命中率也要保证在90%,如果低于这个方式标准可能就需要优化缓存的使用方式了。

既然缓存的使用会带来这么大的影响,到底改如何提高缓存的命中率,到底如何应对缓存穿透呢?

什么是缓存穿透

缓存穿透是指从缓存中没有查到数据,不得不从后端系统比如数据库中查询的情况。

不过少量的穿透必可避免。对系统也是没有伤害的,主要以下原因:

那么什么样的缓存穿透会导致系统崩溃呢?其实指的就是这部分热点数据的穿透请求导致后端服务或数据库处理不过来,甚至有可能系统挂掉。

缓存穿透的解决方案

先看一种场景:电商系统的用户表中,我们需要通过用户Id查询用户信息,缓存读写策略采用cache aside策略。

那么如果要读取一个用户表中未注册的用户,会发生什么情况呢?按照这个策略,我们先读缓存再穿透数据库。由于用户不存在,所以缓存数据库都没有查到数据,因为也就不像缓存中回种数据,这样当再次请求的时候,还是会再次穿透到数据库,这种场景下缓存并不能有效阻挡请求穿透到数据库。那么如何解决呢?回种空值以及使用布隆过滤器。

回种空值

上面的情况容易导致缓存穿透的发生。其实还有一种情况,就是代码的问题导致查询数据库发生了异常,这时也是永远没有获取到数据。

那么 当我遇到这种情况的时候,可以先向缓冲中回种一个空值,因为没有具体业务含义,所以不会占用太多缓存空间。但是我们会给这个空值添加一个极短的过期时间,让空值可以快速的淘汰。

回种空值虽然可以阻挡大量的穿透请求,但是如果有大量未注册的用户信息,缓存内会有大量未注册的用户信息,也会占用空间。所以这个方案的时候,要做下评估 看看容量是不是支持。如果需要大量的缓存节点来支持,这种方案就不建议采用,可以考虑使用布隆过滤器。

使用布隆过滤器

1970年布隆提出了一种布隆过滤器的算法,用来判断一个元素是否在一个集合中,这种算法由一个二进制数据和一个Hash算法组成,基本思路如下:

把集合中的每个值按照提供的hash算法算出对应的hash值,然后将hash值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从0改成1 。在判断一个元素是否存在于这个集合时,只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为 1 就认为这个元素在集合中,否则认为不在集合中。

下图为布隆过滤器的示意图:

15 | 缓存的使用姿势(三):缓存穿透了怎么办?

A、B、C 等元素组成了一个集合,元素D计算出来的hash值所对应的数组值是 1 ,所以可以认为D也在集合中,而F在数组中的值是0,所以F不在数组中。

那么我们如何使用布隆过滤器来解决缓存穿透的问题呢?

新注册用户除了需要写入到数据库中之外,也需要依照同样的算法更新布隆过滤器的数组中响应的值,那么当我们需要查询某个用户信息的时候,先查询是这个id 在布隆过滤器中是否存在,如果不存在就直接返回空值,而不需要继续查询数据库和缓存,这样就可以极大的减少异常带来的缓存穿透。

15 | 缓存的使用姿势(三):缓存穿透了怎么办?

布隆过滤器具有极高的性能,无论写入还是读取操作,时间复杂度都是O(1) 是常量值,在空间上,相对于其他数据结构也有很大的优势,比如,20 亿的数组需要 2000000000/8/1024/1024 = 238M 的空间,而如果使用数组来存储,假设每个用户 ID 占用 4 个字节的空间,那么存储 20 亿用户需要 2000000000 * 4 / 1024 / 1024 = 7600M 的空间,是布隆过滤器的 32 倍。

不过事务都有两面性,布隆过滤器也不例外,主要有两个缺陷:

  1. 他判断元素是否在集合有一定的错误几率,比如它会把不是集合中的元素判断为集合中的元素。
  2. 不支持删除元素

关于第一个缺陷,主要是 Hash 算法的问题。不同的输入值经过hash运算有可能得到相同的结果,这就是碰撞率的概念。

你可能会问为什么不映射成更长的 Hash 值呢?

那么为什么不映射成更长的hash值 会带来更高的存储成本和计算成本。

为了减少碰撞的几率 ,我们可以发生两次Hash,只有两次Hash 的数值都相同了,才认为这个元素在集合中。

布隆过滤器不支持删除的因素还有 ,如果两个元素的 hash值 相同,都对应集合中同一个位置,入股删除,就会影响到另一个元素的判断。

总结

总的来说 回种空值并设置极端缓存时间、布隆过滤器 都是解决缓存穿透的两种方案,但是他们有各自使用的场景,但是并不能解决所有问题。比如有一个极热点的缓存,一旦失效就会有大量请求穿透到数据库,这回对数据库造成极大的压力,我们把这个现象成为 狗庄效应。

这就是典型的缓存穿透场景,那么如何解决呢?思路就是尽量减少缓存穿透后的并发。

  1. 代码中控制某一个热点缓存失效之后,启动一个后台线程,穿透数据库,将数据加载到缓存中,在缓存未加载前所有访问这个缓存的请求都直接返回 不穿透后端。
  2. 通过在 Memcached 或者 Redis 中设置分布式锁,只有获取到锁的请求才能够穿透到数据库。

通过 本文相信你已经了解了 处理缓存穿透的集中方式:

  1. 回种空值的方式,也是最常见的一种思路。如果评估空值缓存占用空间可以接收的话,可以优先适用这种方式。
  2. 引入新组件布隆过滤器,也会引入开发和运维上的一些成本。所以只有再海量数据查询时候才会适用,使用时要关注布隆过滤器对内存空间的消耗。
  3. 对于机热点数据对缓存穿透造成的“狗桩效应”,可以通过分布式锁及后台线程的方式去搞定。
展开阅读全文

页面更新:2024-03-11

标签:缓存   热点   命中率   数组   过滤器   用户信息   算法   姿势   元素   发生   方式   数据库   数据   用户   系统   科技   空间

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top