高并发场景下,缓存“穿透”了该怎么办?

发布时间:2023年12月31日

通常情况下,被访问的数据即使不在缓存当中,请求也是可以访问数据库然后将结果回填到缓存中的。缓存“穿透”是指,访问的 key 并不存在(即业务系统中没有这个 key),之后请求“穿透”到数据库中,从数据库查询出来的是空值(NULL)。

用户访问一个不存在的 key,“穿透”到数据库中,数据库返回空值,并不会回填到缓存中,那么后续不管查询这个key多少次,都会缓存命中失败,直接“穿透”到数据库中。这样会严重影响数据库的性能。

如果是恶意破坏的请求,或者恶意地大量访问不存在的key,则会使系统的整体性能严重下降,并最终影响正常的用户请求。

1. 解决方案

大量的缓存“穿透”会对系统产生致命的影响,所以,对于缓存“穿透”需要重视起来。一般有以下两种解决方案。

(1)回种空值。

发生“穿透”的原因是,在数据库中根本不存在被访问的数据。这样无论访问多少次都获取不到这些数据,“穿透”会一直发生。

所以,在访问不存在的数据时,虽然第一次“穿透”到数据库获取的是空值(NULL),但可以用这个空值(NULL)回填缓存(称为“回种空值”)。

但是空值并不是真实的业务数据,如果有人恶意访问这些并不存在的 key,则会占用很大的内存空间,从而将正常的业务 key 给自动淘汰掉。所以,在将这些不存在 key 写入缓存时,可以加上一个很短的有效期以使其快速过期,伪代码如下:

Object nullValue = new Object();
    try {    
        // 从数据库中查询数据
        Object valueFromDB = selectFromDB(uid);
        if (valueFromDB == null {
            // 如果从数据库查询到空值,则把空值写入缓存,并设置较短的超时时间
            redisCache.set(uid, nullValue, 10);
        } else {
            redis.Cache.set(uid, valueFromDB, 1000);
        }
    } catch Exception(e) {    
        redisCache.set(uid, nullValue, 10)
    }

回种空值这种方案,会阻挡很大一部分的“穿透”请求。但是如果大批量地访问不存在的 key,则这种方案会造成缓存容量的紧张,甚至占满内存空间。

在具体实施过程中。如果要解决这种“穿透”,则需要额外增加很多的缓存节点,有点得不偿失,此时可以考虑使用第二种方案“实用布隆过滤器”。

(2)使用布隆过滤器

布隆过滤器用来检测一个元素是否在一个集合中。这种算法由一个二进制数组加上一个Hash算法组成。其基本原理如下:

  1. 数据结构: 布隆过滤器通常由一个位数组(Bit Array)和一组哈希函数组成。

  2. 位数组: 位数组是布隆过滤器的主要存储结构,它通常初始化为全部为0。每个元素在位数组中对应多个位,初始时都被设为0。

  3. 哈希函数: 布隆过滤器需要 k 个哈希函数。这些哈希函数可以将任意大小的数据映射到位数组的某个位置。对于每个元素,通过这 k 个哈希函数计算出 k 个哈希值,然后将对应的位数组位置设为1。

  4. 插入操作: 当向布隆过滤器中插入一个元素时,通过 k 个哈希函数计算出 k 个哈希值,并将对应的位数组位置设为1。

  5. 查询操作: 当查询一个元素是否存在于集合中时,同样通过 k 个哈希函数计算出 k 个哈希值,然后检查这 k 个位置的值。如果所有的位置都为1,则认为元素可能存在;如果有任何一个位置为0,则元素一定不存在。

由于哈希函数的存在,布隆过滤器有一定的误判率,即可能存在 "误判为存在" 的情况,但不存在 "误判为不存在" 的情况。

优点:

  • 布隆过滤器的查询时间复杂度是常量级别的,非常高效。
  • 空间效率较高,因为只需要存储位数组和少量哈希函数。

缺点:

  • 有一定的误判率。
  • 不支持删除操作,删除一个元素可能会影响其他元素的判断结果。

应用场景:

  • 缓存系统,用于快速判断某个数据是否在缓存中。
  • 防止缓存穿透,即防止查询不存在的数据频繁地访问数据库。
  • 分布式系统中的去重操作等。

文章来源:https://blog.csdn.net/weixin_55252589/article/details/135319495
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。