如何解决缓存穿透问题:布隆过滤器与空值缓存方案对比

来源:菜鸟站长作者:广州网站建设头衔:草根站长
导读:本期聚焦于广州网站建设创作的《如何解决缓存穿透问题:布隆过滤器与空值缓存方案对比》,敬请观看详情。缓存穿透指查询不存在的数据时请求直击数据库,高并发下极易引发宕机。布隆过滤器以位数组与哈希函数预判键是否存在,内存占用极低但存在误判且不支持删除。空值缓存则将查不到的结果暂存为占位符,实现简单却能撑大内存。二者在误判率、删除支持与实现成本上差异明显,实际系统常组合使用:过滤器拦掉大部分非法请求,空值缓存兜底偶发穿透,并配合过期与互斥重建降低风险。

缓存穿透是分布式系统中常见的性能隐患,当客户端频繁查询根本不存在的数据时,缓存层因查不到而放行请求,压力直接传导到数据库。如果恶意流量或业务bug持续发起此类查询,数据库连接池会被迅速耗尽。要根治这一问题,业界通常采用布隆过滤器提前拦截,或者对空结果进行缓存。这两种思路在原理和适用场景上有明显区别,理解它们的底层机制才能正确落地。

如何解决缓存穿透问题:布隆过滤器与空值缓存方案对比

布隆过滤器的底层原理与实现

布隆过滤器本质上是一个超长的位数组以及多个相互独立的哈希函数。当一个元素加入集合时,通过k个哈希函数计算出k个位置,将位数组中对应下标置为1。查询时同样计算这些位置,只要有一个位置为0,就说明该元素一定不在集合中;若全部为1,则元素可能在集合中。这种概率型数据结构不用保存原数据,因此空间效率极高,特别适合用来判断海量key是否合法。

在Java中可以利用Guava或Redisson实现布隆过滤器。下面示例展示基于Guava的本地过滤器用法,实际生产常将其放入Redis以保证多实例共享。需要注意,布隆过滤器无法删除元素,因为某个位可能由多个key共用,直接置0会影响其他key的判定。

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class BloomDemo {
    // 预计插入100万数据,误判率0.01
    private static BloomFilter<String> filter = BloomFilter.create(
            Funnels.stringFunnel(java.nio.charset.StandardCharsets.UTF_8),
            1000000,
            0.01);

    public static void main(String[] args) {
        filter.put("user_1");
        filter.put("user_2");
        // 返回false表示一定不存在
        System.out.println(filter.mightContain("user_999"));
    }
}

布隆过滤器的主要优势是内存占用小、查询速度快,缺点则是存在误判率且不支持删除。当业务需要下线某些key时,往往只能重建整个过滤器。此外,如果攻击者构造出恰好误判的key,仍会穿透到数据库,因此通常要配合其他手段使用。

空值缓存的设计与注意事项

空值缓存的思路非常直观:当数据库也查不到数据时,在缓存中写入一个特殊的空对象或null标记,并设置一个较短的过期时间。后续相同请求命中缓存中的空值,直接返回,不再访问数据库。该方案实现成本极低,不需要引入额外组件,适合大部分中小型系统。

使用空值缓存时要重点解决两个问题。一是空值过期时间不宜过长,否则真实数据插入后用户仍长时间看到空结果;二是要防止恶意刷大量不同不存在的key,导致缓存被空值占满。可以结合布隆过滤器先过滤非法key,或者对空值采用更短的TTL与最大数量限制。

import redis

r = redis.Redis(host='127.0.0.1', port=6379, db=0)

def get_user(uid):
    key = "user:" + str(uid)
    val = r.get(key)
    if val is not None:
        # 空值标记为字符串NULL
        return None if val == b"NULL" else val
    # 数据库查询
    data = db_query(uid)
    if data is None:
        # 缓存空值60秒
        r.setex(key, 60, "NULL")
        return None
    r.setex(key, 3600, data)
    return data

空值缓存的劣势在于,如果穿透key的随机性极强,缓存层会存储大量无用占位符,浪费内存并可能驱逐热点数据。因此它与布隆过滤器并非互斥,而是互补关系:前者拦住格式非法或明显无关的请求,后者兜住偶发的查无结果。

组合方案与高并发下的优化策略

在真实高并发场景中,单靠一种手段难以覆盖所有情况。推荐架构是在网关或缓存前增加布隆过滤器,将系统允许的有效key范围固化;当请求通过过滤器后,若缓存未命中再查数据库,并将空结果按短TTL缓存。这样即便过滤器误判,空值缓存也能阻断重复查询对数据库的冲击。

此外还需考虑缓存击穿与雪崩的连带影响。可以使用互斥锁或逻辑过期来重建缓存,避免大量线程同时回源。对于空值缓存的写入,建议增加随机过期偏移,防止同一时刻大批空值一起失效。下表对比两种核心方案的关键指标:

方案内存占用误判率删除支持实现复杂度
布隆过滤器极低可配置不支持
空值缓存随空key增长支持

落地时还应做好监控,统计穿透请求比例与数据库负载。若发现过滤器误判率升高,可适当增大位数组或调整哈希函数数量。若空值缓存命中率过高,说明可能存在大量非法查询,需要从业务入口或风控层面限制。通过多层防护,缓存穿透问题才能被稳定解决。

缓存穿透布隆过滤器空值缓存修改时间:2026-08-18 23:08:26

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。