分布式之缓存击穿

大数据量下的集合过滤—Bloom Filter 算法背景如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路,存储位置要么是磁盘,要么是内存。很多时候要么是以时间换空间,要么是以空间换时间。在响应时间要求比较严格的情况下,如果我们存在内里,那么随着集合中元素的增加,我们需要的存储空间越来越大,以及检索的时间越来越长,导致内存开销... 阅读详情

原文:分布式之缓存击穿

什么是缓存击穿

在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示
image

因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义。如果在大流量下数据库可能挂掉。这就是缓存击穿。
场景如下图所示:
image

我们正常人在登录首页的时候,都是根据userID来命中数据,然而黑客的目的是破坏你的系统,黑客可以随机生成一堆userID,然后将这些请求怼到你的服务器上,这些请求在缓存中不存在,就会穿过缓存,直接怼到数据库上,从而造成数据库连接异常。

解决方案

在这里我们给出三套解决方案,大家根据项目中的实际情况,选择使用.

讲下述三种方案前,我们先回忆下redis的setnx方法

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

可用版本:>= 1.0.0

时间复杂度: O(1)

返回值: 设置成功,返回 1。设置失败,返回 0 。

效果如下

redis> EXISTS job                # job 不存在

(integer) 0

redis> SETNX job "programmer"    # job 设置成功

(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败

(integer) 0

redis> GET job                   # 没有被覆盖

"programmer"

1、使用互斥锁

该方法是比较普遍的做法,即,在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试。

至于锁的类型,单机环境用并发包的Lock类型就行,集群环境则使用分布式锁( redis的setnx)

集群环境的redis的代码如下所示:

String get(String key) {  

   String value = redis.get(key);  

   if (value  == null) {  

    if (redis.setnx(key_mutex, "1")) {  

        // 3 min timeout to avoid mutex holder crash  

        redis.expire(key_mutex, 3 * 60)  

        value = db.get(key);  

        redis.set(key, value);  

        redis.delete(key_mutex);  

    } else {  

        //其他线程休息50毫秒后重试  

        Thread.sleep(50);  

        get(key);  

    }  

  }  

}  

优点:

  1. 思路简单
  2. 保证一致性

缺点

  1. 代码复杂度增大
  2. 存在死锁的风险

2、异步构建缓存

在这种方案下,构建缓存采取异步策略,会从线程池中取线程来异步构建缓存,从而不会让所有的请求直接怼到数据库上。该方案redis自己维护一个timeout,当timeout小于System.currentTimeMillis()时,则进行缓存更新,否则直接返回value值。
集群环境的redis代码如下所示:

String get(final String key) {  

        V v = redis.get(key);  

        String value = v.getValue();  

        long timeout = v.getTimeout();  

        if (v.timeout <= System.currentTimeMillis()) {  

            // 异步更新后台异常执行  

            threadPool.execute(new Runnable() {  

                public void run() {  

                    String keyMutex = "mutex:" + key;  

                    if (redis.setnx(keyMutex, "1")) {  

                        // 3 min timeout to avoid mutex holder crash  

                        redis.expire(keyMutex, 3 * 60);  

                        String dbValue = db.get(key);  

                        redis.set(key, dbValue);  

                        redis.delete(keyMutex);  

                    }  

                }  

            });  

        }  
        return value;  
    }

优点:

  1. 性价最佳,用户无需等待

缺点

  1. 无法保证缓存一致性

3、布隆过滤器

1、原理

布隆过滤器的巨大用处就是,能够迅速判断一个元素是否在一个集合中。因此他有如下三个使用场景:

  1. 网页爬虫对URL的去重,避免爬取相同的URL地址
  2. 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)
  3. 缓存击穿,将已存在的缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。

OK,接下来我们来谈谈布隆过滤器的原理
其内部维护一个全为0的bit数组,需要说明的是,布隆过滤器有一个误判率的概念,误判率越低,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。

假设,根据误判率,我们生成一个10位的bit数组,以及2个hash函数((f_1,f_2)),如下图所示(生成的数组的位数和hash函数的数量,我们不用去关心是如何生成的,有数学论文进行过专业的证明)。
image

假设输入集合为((N_1,N_2)),经过计算(f_1(N_1))得到的数值得为2,(f_2(N_1))得到的数值为5,则将数组下标为2和下表为5的位置置为1,如下图所示
image

同理,经过计算(f_1(N_2))得到的数值得为3,(f_2(N_2))得到的数值为6,则将数组下标为3和下表为6的位置置为1,如下图所示
image

这个时候,我们有第三个数(N_3),我们判断(N_3)在不在集合((N_1,N_2))中,就进行(f_1(N_3),f_2(N_3))的计算

  1. 若值恰巧都位于上图的红色位置中,我们则认为,(N_3)在集合((N_1,N_2))中
  2. 若值有一个不位于上图的红色位置中,我们则认为,(N_3)不在集合((N_1,N_2))中

以上就是布隆过滤器的计算原理,下面我们进行性能测试,

2、性能测试

代码如下:

(1)新建一个maven工程,引入guava包

   <dependencies>  

        <dependency>  

            <groupId>com.google.guava</groupId>  

            <artifactId>guava</artifactId>  

            <version>22.0</version>  

        </dependency>  

    </dependencies>

(2)测试一个元素是否属于一个百万元素集合所需耗时

package bloomfilter;



import com.google.common.hash.BloomFilter;

import com.google.common.hash.Funnels;

import java.nio.charset.Charset;



public class Test {

    private static int size = 1000000;


    private static BloomFilter<Integer> bloomFilter = 
    BloomFilter.create(Funnels.integerFunnel(), size);

    public static void main(String[] args) {

        for (int i = 0; i < size; i++) {

            bloomFilter.put(i);

        }

        long startTime = System.nanoTime(); // 获取开始时间     

        //判断这一百万个数中是否包含29999这个数

        if (bloomFilter.mightContain(29999)) {

            System.out.println("命中了");

        }

        long endTime = System.nanoTime();   // 获取结束时间

        System.out.println("程序运行时间: " + (endTime - startTime) + "纳秒");

    }

}

输出如下所示

1

2

命中了

程序运行时间: 219386纳秒

也就是说,判断一个数是否属于一个百万级别的集合,只要0.219ms就可以完成,性能极佳。

(3)误判率的一些概念

首先,我们先不对误判率做显示的设置,进行一个测试,代码如下所示

package bloomfilter;


import java.util.ArrayList;

import java.util.List;

import com.google.common.hash.BloomFilter;

import com.google.common.hash.Funnels;


public class Test {

    private static int size = 1000000;

    private static BloomFilter<Integer> bloomFilter = 
    BloomFilter.create(Funnels.integerFunnel(), size);

    public static void main(String[] args) {

        for (int i = 0; i < size; i++) {

            bloomFilter.put(i);

        }

        List<Integer> list = new ArrayList<Integer>(1000);     

        //故意取10000个不在过滤器里的值,看看有多少个会被认为在过滤器里

        for (int i = size + 10000; i < size + 20000; i++) {  

            if (bloomFilter.mightContain(i)) {  

                list.add(i);  

            }  

        }  

        System.out.println("误判的数量:" + list.size());

    }

}

输出结果如下

1

误判对数量:330

如果上述代码所示,我们故意取10000个不在过滤器里的值,却还有330个被认为在过滤器里,这说明了误判率为0.03.即,在不做任何设置的情况下,默认的误判率为0.03。
下面上源码来证明:
image

接下来我们来看一下,误判率为0.03时,底层维护的bit数组的长度如下图所示
image

将bloomfilter的构造方法改为

 

1

private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size,0.01);

即,此时误判率为0.01。在这种情况下,底层维护的bit数组的长度如下图所示
image
由此可见,误判率越低,则底层维护的数组越长,占用空间越大。因此,误判率实际取值,根据服务器所能够承受的负载来决定,不是拍脑袋瞎想的。

3、实际使用

redis伪代码如下所示

String get(String key) {  

   String value = redis.get(key);  

   if (value  == null) {  

        if(!bloomfilter.mightContain(key)){

            return null;

        }else{

           value = db.get(key);  

           redis.set(key, value);  

        }

    }

    return value;

}

优点:

  1. 思路简单
  2. 保证一致性
  3. 性能强

缺点

  1. 代码复杂度增大
  2. 需要另外维护一个集合来存放缓存的Key
  3. 布隆过滤器不支持删值操作

总结

在总结部分,来个漫画把。希望对大家找工作有帮助
image

分布式项目redis缓存富用-穿透,雪崩,击穿 redisson锁 高并发下大量请求查询一个一定不存在的数据,由于缓存中没有命中导致每次请求都会引起服务对db的链接调用,从而引起数据库压力激增的崩溃现象就是缓存穿透的行为高并发下,不论是缓存时间到期还是其他原因,大量的缓存数据同时失效,又同时被请求,无法命中时,大量的缓存需要从db中查询,导致数据库压力瞬间升高引起的崩溃现象。 阅读详情

相关推荐

深入理解分布式技术 - 探究缓存穿透、缓存击穿、缓存雪崩解决方案

文章目录概述缓存穿透what缓存穿透发生的场景举例不合理的缓存失效策略恶意攻击如何规避缓存穿透 概述 缓存系统不得不考虑的问题是缓存穿透、缓存击穿与失效时的雪崩效应,这是避免不了的问题 。 今天我们来讨论下这几种问题的常见的解决思路 缓存穿透 what 顾名思义,是指业务请求穿过了缓存层,落到持久化存储上。在大多数场景下,我们应用缓存是为了承载前端业务请求,缓存被击穿以后,如果请求量比较大,则会导致数据库出现风险。 高并发的情况下, 单靠数据库是不能承载的,如果缓存不能很好的工作,可能会影响数据库

小工匠 2万+

[ Spark ] java.lang.NoSuchMethodError: com.google.common.hash.Funnels.stringFunnel

Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.hash.Funnels.stringFunnel(Ljava/nio/charset/Charset;)Lcom/google/common/hash/Funnel; at BloomFilterCase$.main(BloomFilterCase.scala:11) at BloomFilterCase.main(BloomFilterCase.scal

爪白白的个人博客 3486

OGG工具实现MySQL同步到Oracle数据库

OGG工具实现MySQL同步数据到Oracle

Mrzhou的博客 1682

Google布隆过滤器与Redis布隆过滤器

一、什么是布隆过滤器? 布隆过滤器可以用来判断一个元素是否在一个集合中。它的优势是只需要占用很小的内存空间以及有着高效的查询效率 对于布隆过滤器而言,它的本质是一个位数组:位数组就是数组的每个元素都只占用1bit ,并且每个元素只能是0或者1 布隆过滤器除了一个位数组,还有K个哈希函数。当一个元素加入布隆过滤器中的时候,会进行如下操作: 使用K个哈希函数对元素值进行K次计算,得到K个哈希值 根据得到的哈希值,在位数组中把对应下标的值置为1 下图表示有三个hash函数,比如一个集合中有x、y、z三个元素,分别

Jiangbohao_的博客 352

ZYNQ烧写程序到flash

1.打开SDK创建FBSl项目 点击finish 2.创建BootImage 导入fsbl.elf、.bin、.elf三个文件,Create Image 生成BOOT.bin文件 3.将BOOT文件烧录到FLASH image File选择2中生成的BOOT.bin文件,FSBL File选择fsbl.elf文件,连接好ZYNQ的Jtag,点击Program,即可烧录。 4.烧录程序到FLASH失败 如果是第一次烧录,需更改创建的fsbl工程下的main函数,在如下代码处加入BootModeRegi

qq_40603614的博客 8292

​苹果开发者账号推送证书配置详细指南​

推送证书的配置是iOS应用实现消息推送的核心步骤。通过生成证书、配置描述文件、集成到服务器三大环节,开发者可快速为App启用APNs服务。建议优先使用更灵活的APNs Auth Key替代传统证书,以减少维护成本。确保拥有有效的苹果开发者账号(个人/公司账号),年费已缴纳。

2324

使用布隆过滤器解决缓存穿透

手写布隆过滤器 ** 过滤器代码** package com.ming.filter; import com.google.common.hash.Funnels; import com.google.common.hash.Hashing; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springf

weixin_47395433的博客 413

Mplus实战:三变量随机截距交叉滞后模型(RI-CLPM)语法详解与扩展

本文详细解析了Mplus中三变量随机截距交叉滞后模型(RI-CLPM)的构建原理与语法实现。通过将变量分解为稳定的个体间差异与动态的个体内波动,模型能更纯净地揭示多变量间的纵向交互影响。文章以工作压力、睡眠质量与工作满意度为例,提供了完整的语法模板、关键参数设置(如NOCOV命令、残差相关)及结果解读指南,帮助研究者避开常见误区,稳健分析复杂动态关系。

wdx0123456的博客 926

布隆过滤器的原理与实现

前几天看视频,无意中看到一个Redis缓存会存在三大问题,分别是缓存穿透,缓存击穿,缓存雪崩。在处理缓存穿透的时候,介绍了两种方案,推荐使用布隆过滤器,还有一个是Redis中缓存null。之前只是有听过布隆过滤器,但是对其原理和特点,使用场景并不熟悉。于是网上查阅了挺多资料。总结成为自己看得懂的一篇个人对于布隆过滤器的理解文章。 布隆过滤器,英文 Bloom Filter。简单理解是一个叫布隆的人开发的一个过滤器,过滤器我的理解就是为了进行逻辑上的过滤校验。布隆过滤器是一个...

cssweb_sh的博客 1823

8台 × 8卡 910B NPU之顶尖大模型选型分析

本文对比了2026年主流大语言模型在昇腾910B NPU集群上的部署方案。重点评估了GLM5、MiniMax M2.5、Kimi K2.5、DeepSeek V3.2和Qwen 3.5旗舰版等候选模型,从部署需求(显存占用)、能力表现(基准测试、多模态支持)和NPU生态适配度三个维度进行分析。结果显示DeepSeek V3.2综合表现最优:支持BF16/W8A8/INT4量化,在4TB显存环境下可实现2000+ TPS,首token延迟<100ms,且具备官方NPU支持。备选方案Qwen 3.5旗舰版

FeelTouch Labs 754

【Dify系列教程重置精品版】第二章:在Windows上部署Dify

让我们的电脑允许使用Linux系统。有点兴奋是怎么回事?简单来说就是让你的电脑拥有另一个叫做Linux的系统。3.选择“适用于Linux的Windows子系统”与 “虚拟机平台”与“Hyper-V"第1章:安装 wsl (Windows Subsystem for Linux)上一章我们聊了什么是Dify,这一章我们讲一下怎么才能用Dify。好了,我们的环境部署好了,下一章我们正式进入Dify。下载完成后,我们直接一直无脑下一步,安装即可。完成上述步骤,我们就可以开始安装wsl了。我们的虚拟化也是开启的。

xiezhipu的博客 866

Redis主从复制、缓存雪崩、缓存击穿、缓存穿透及分布式缓存(三)

✍、Redis主从复制、缓存雪崩、缓存击穿、缓存穿透及分布式缓存(三)

Augenstern_QXL的博客 2023

OpenRubrics:自动化评估工具优化LLM对齐效果

在大型语言模型(LLM)应用中,模型输出与人类价值观的对齐是关键挑战。传统人工反馈强化学习(RLHF)依赖大量标注,成本高且一致性差。OpenRubrics作为开源工具,通过结构化准则实现自动化评估,显著提升效率。其核心原理是将主观评价转化为可量化的多维指标,支持动态权重调整,并兼容主流RLHF框架。该工具在降低标注成本的同时,通过Fleiss' Kappa系数等指标保证评估一致性,适用于多语言和医疗等专业领域,为LLM对齐提供了工程化解决方案。

weixin_28730037的博客 357

分布式缓存击穿以及本地击穿解决方案

缓存的某个热点数据过期,此时大量的用户请求过来,缓存未命中,然后都打到数据库去,导致数据库压力骤增。

许苑向上 579

【Unity URP渲染管线下Shader的自定义物体半透明和投影半透明效果_半透明案例分享】

Unity Shader Graph URP渲染管线下的自定义半透明效果_半透明案例分享

暴走约伯的博客 3294

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

后端笔记03 | 分布式缓存常见问题及概念:缓存穿透、缓存击穿、缓存雪崩、缓存淘汰

分布式缓存学习笔记,理解布隆过滤器、热key、缓存淘汰、缓存雪崩/击穿/穿透等问题。

2201_75974870的博客 1094

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

151-157、缓存-缓存使用-本地缓存与分布式缓存、整合redis测试、改造三级分类业务、压力测试出的内存泄露及解决、缓存击穿、穿透、雪崩、加锁解决缓存击穿问题、本地锁在分布式下的问题

151、缓存-缓存使用-本地缓存与分布式缓存 152、缓存-缓存使用-整合redis测试 153、缓存-缓存使用-改造三级分类业务 154、缓存-缓存使用-压力测试出的内存泄露及解决 155、缓存-缓存使用-缓存击穿、穿透、雪崩 156、缓存-缓存使用-加锁解决缓存击穿问题 157、缓存-缓存使用-本地锁在分布式下的问题 ...

zouxuhang的博客 229

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

缓存击穿:从分布式场景到本地层面的解决方案探究

本文介绍了缓存击穿问题及其解决方案。缓存击穿指热点数据过期时大量请求直接访问数据库,导致数据库压力骤增。解决方案包括:1)单机环境的双重检索机制,通过两次检查减少加锁开销;2)分布式环境的互斥锁方案,确保只有一个请求重建缓存;3)逻辑过期方案,通过设置逻辑过期时间实现异步更新缓存。三种方案各有优劣,双重检索性能较好但只适用于单机,互斥锁保证一致性但可能递归,逻辑过期性能最佳但存在短暂不一致可能。

Shine19的博客 621

分布式场景下防止【缓存击穿】的不同方案

本文探讨了高并发场景下的缓存击穿问题及解决方案。通过分析热点数据查询案例,提出三种优化方案:1)设置热点数据永不过期;2)使用分布式锁(Redission RLock)防止并发重建缓存;3)在分布式锁基础上引入双重检测机制和tryLock方法优化锁竞争。重点介绍了RLock+双重检测+tryLock的组合方案,该方法通过有限等待时间(如1秒)获取锁,避免无限阻塞,大幅降低系统延迟。同时指出需要根据业务特点合理设置锁等待时间,平衡缓存构建时间与系统性能。

Muyu1uz的博客 1316

golang分布式缓存项目 Day6 防止缓存击穿

singleflight 是一个非常有用的包,它提供了一种机制来抑制对某个函数的多次重复调用。这个包特别适用于避免在高并发场景下对同一资源的重复请求,比如在缓存击穿问题中,多个请求同时访问同一个资源时,singleflight 可以确保这些请求中只有一个实际执行,其他请求则等待这个结果,从而减少对后端服务的压力核心概念singleflight 包中定义了一个名为 Group 的结构体类型,它表示一类工作,并形成一个命名空间,在这个命名空间中,可以使用重复抑制来执行工作单元。

m0_62112384的博客 1784

分布式下缓存穿透,缓存击穿,缓存雪崩

一,缓存处理流程 二,缓存穿透 描述: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1024”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。 解决方法: 1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key...

weixin_43867933的博客 443

分布式缓存Redis击穿、雪崩、穿透面试题+SpringCache解决方案

目录 一、分布式缓存必考题之缓存击穿+解决方案 二、分布式缓存必考题之缓存雪崩+解决方案 三、分布式缓存必考题之缓存穿透+解决方案 一、分布式缓存必考题之缓存击穿+解决方案 缓存击穿 (某个热点key缓存失效了): 缓存中没有但数据库中有的数据,假如是热点数据,那key在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力增大。 和缓存雪崩的区别在于这里针对某一key缓存,后者则是很多key。 可以用这种方式来预防: 设置热点数据......

点滴记忆,分享成长之路 2336

【Go | 从0实现简单分布式缓存】-4:使用singleflight防止缓存击穿

主要介绍如何使用singlefligth防止缓存击穿,途中涉及到了一些经典的函数,比如协程的wg.WaitGroup()函数应该在协程之间不需要消息传递的时候非常适用,还有对应的匿名函数的作用原理。

theaipower的博客 1285

分布式—缓存击穿

先了解一下定义: 什么是缓存击穿? 在讲解缓存击穿之前,我们先来看下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义。如果在大流量下数据库可能挂掉。这就是缓存击穿。 场景图如下所示: 我们正常人在登录首页的时候,都是根据userID来命中数据,然而黑客的目的是破坏你的系统,黑客可以随机生成一堆use...

lemonrel的博客 156

【业务功能篇87】微服务-springcloud-本地缓存-redis-分布式缓存-缓存穿透-雪崩-击穿

缓存的作用是减低对数据源的访问频率。从而提高我们系统的性能。缓存的流程图。

studyday1的博客 1149
上一篇: 关于分布式事务、两阶段提交协议、三阶提交协议
下一篇: 布隆过滤器(Bloom Filter)详解
shadow_zed
博客等级 码龄10年 6289粉丝 68原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值