Redis缓存击穿

2023-11-03

  1. 什么是缓存击穿
    在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示
    因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义。如果在大流量下数据库可能挂掉。这就是缓存击穿。
    我们正常人在登录首页的时候,都是根据userID来命中数据,然而黑客的目的是破坏你的系统,黑客可以随机生成一堆userID,然后将这些请求怼到你的服务器上,这些请求在缓存中不存在,就会穿过缓存,直接怼到数据库上,从而造成数据库连接异常。
  2. 解决方案
    在这里我们给出三套解决方案,大家根据项目中的实际情况,选择使用.
    讲下述三种方案前,我们先回忆下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"

2.1. 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);  

    }  

  }  

}  

优点

思路简单
保证一致性
缺点
代码复杂度增大
存在死锁的风险

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;  

    }

优点
性价最佳,用户无需等待
缺点
无法保证缓存一致性
2.3布隆过滤器
1、原理
布隆过滤器的巨大用处就是,能够迅速判断一个元素是否在一个集合中。因此他有如下三个使用场景:
网页爬虫对URL的去重,避免爬取相同的URL地址
反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)
缓存击穿,将已存在的缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。
OK,接下来我们来谈谈布隆过滤器的原理
其内部维护一个全为0的bit数组,需要说明的是,布隆过滤器有一个误判率的概念,误判率越低,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。
假设,根据误判率,我们生成一个10位的bit数组,以及2个hash函数((f_1,f_2)),如下图所示(生成的数组的位数和hash函数的数量,我们不用去关心是如何生成的,有数学论文进行过专业的证明)。
假设输入集合为((N_1,N_2)),经过计算(f_1(N_1))得到的数值得为2,(f_2(N_1))得到的数值为5,则将数组下标为2和下表为5的位置置为1,如下图所示
同理,经过计算(f_1(N_2))得到的数值得为3,(f_2(N_2))得到的数值为6,则将数组下标为3和下表为6的位置置为1,如下图所示
这个时候,我们有第三个数(N_3),我们判断(N_3)在不在集合((N_1,N_2))中,就进行(f_1(N_3),f_2(N_3))的计算
若值恰巧都位于上图的红色位置中,我们则认为,(N_3)在集合((N_1,N_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) + "纳秒");
    }

}

输出如下:

命中了

程序运行时间: 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());
    }

}

输出如下:

误判对数量:330

如果上述代码所示,我们故意取10000个不在过滤器里的值,却还有330个被认为在过滤器里,这说明了误判率为0.03.即,在不做任何设置的情况下,默认的误判率为0.03。
下面上源码来证明:
接下来我们来看一下,误判率为0.03时,底层维护的bit数组的长度如下图所示
将bloomfilter的构造方法改为

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

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

}

优点:
思路简单
保证一致性
性能强
缺点:
代码复杂度增大
需要另外维护一个集合来存放缓存的Key
布隆过滤器不支持删值操作

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis缓存击穿 的相关文章

  • redis 2.8.7 Linux Sentinel环境配置问题,如何使其自启动,应该订阅什么?

    现在我们尝试使用 redis 2 8 7 作为缓存存储 来自使用 booksleeve 客户端的 NET Web 应用程序 目前看来这是一个非常有趣和令人兴奋的任务 redis 文档非常好 但由于缺乏真正的实践经验 我确实有几个关于如何正确
  • 如何将“.csv”数据文件导入Redis数据库

    如何将 csv 数据文件导入 Redis 数据库 csv 文件中包含 id 时间 纬度 经度 列 您能否向我建议导入 CSV 文件并能够执行空间查询的最佳方法 这是一个非常广泛的问题 因为我们不知道您想要什么数据结构 您期望什么查询等等 为
  • 创建 C++ Redis 模块 - “不导出 RedisModule_OnLoad() 符号”

    我在加载 Redis 模块时遇到一些问题 我只是复制来自的示例https redis io topics modules intro https redis io topics modules intro 但我把它剥下来了 include
  • 当 Jedis 与 Spring Data 一起使用时,为什么数据会以奇怪的键存储在 Redis 中?

    我将 Spring Data Redis 与 Jedis 一起使用 我正在尝试存储带有密钥的哈希值vc list id 我能够成功插入到redis 但是 当我使用 redis cli 检查密钥时 我没有看到密钥vc 501381 相反我看到
  • 如何延长 django-redis 中的缓存 ttl(生存时间)?

    我正在使用 django 1 5 4 和 django redis 3 7 1 我想延长缓存的 ttl 生存时间 当我取回它时 这是示例代码 from django core cache import cache foo cache get
  • Spring Data Redis 覆盖默认序列化器

    我正在尝试创建一个RedisTemplatebean 将具有更新的值序列化器来序列化对象JSONredis 中的格式 Configuration class RedisConfig Bean name redisTemplate Prima
  • 将文件传递给活动作业/后台作业

    我通过标准文件输入接收请求参数中的文件 def create file params file upload Upload create file file filename img png end 但是 对于大型上传 我想在后台作业中执行
  • 如何在Redis中正确存储图片?

    决定将图像存储在Redis中 如何正确执行 现在我这样做 redis gt set image path here is the base64 image code 我不确定这是否正常 将图片存储在Redis中是完全可以的 Redis 键和
  • 为什么我们需要 Redis 来运行 CKAN?

    我想知道为什么我们需要 Redis 服务器来运行 CKAN 如果需要 为什么 我如何使用 CKAN 配置它 附注 我正在 RHEL7 中运行我的 ckan 实例 Update Redis 已成为一项要求从CKAN 2 7开始 https d
  • JedisPoolConfig 不可分配给 GenericObjectPoolConfig

    我有一个基于 Spring 的 Java Web 应用程序托管在 Heroku 上 我正在尝试使用 Redis 实现来利用 Spring 缓存抽象 当服务器启动时 我收到一条错误消息 Type redis clients jedis Jed
  • redis.exceptions.ConnectionError:连接到本地主机时出现错误-2:6379。名称或服务未知

    当我在服务器中运行代码时出现此错误 我的环境是 debian 并且Python2 7 3 Traceback most recent call last File fetcher py line 4 in
  • Redis AOF fsync(始终)与 LSM 树

    我对日志结构化合并树 LSM 树 的理解是 它利用了附加到磁盘非常快 因为它不需要查找 这一事实 只需将更新附加到预写日志并返回到客户端即可 我的理解是 这仍然提供了立即的持久性 同时仍然非常快 我不认为 Redis 使用 LSM 树 它似
  • 如何在Redis中使用HSCAN命令?

    我想在我的作业中使用 Redis 的 HSCAN 命令 但我不知道它是如何工作的 Redis 的官方页面 http redis io commands hscan http redis io commands hscan 这个命令给了我空白
  • 如何按键中的值对 Redis 哈希进行排序

    Redis 有没有一种好方法来获取按值排序的哈希中的键 我查看了文档 但没有找到直接的方法 另外有人可以解释一下redis中的排序是如何实现的 以及什么吗 本文档 http redis io commands SORT using hash
  • 如何暂停或恢复 celery 任务?

    我的项目中有一项要求 客户可以暂停或恢复正在挂起的流程 而不是流程流程 我在用网络套接字显示芹菜任务结果 但在暂停 恢复时我不明白如何设计代码 我想到的唯一方法就是revoke暂停请求中的任务 同时保留数据撤销的过程在缓存中 并稍后在res
  • 如果 Redis 已经是堆栈的一部分,为什么 Memcached 仍然与 Redis 一起使用?

    Redis 可以执行 Memcached 提供的所有操作 LRU 缓存 项目过期以及现在版本 3 x 中的集群 目前处于测试阶段 或通过 twemproxy 等工具执行 性能也类似 此外 Redis 增加了持久性 因此您无需在服务器重新启动
  • node_redis CONFIG SET 命令

    我目前正在使用 redis 编写一个应用程序 但我遇到了 node redis 库的问题 特别是我无法弄清楚如何从 node redis 中使用 redis 命令 我已经尝试了以下所有 client send command CONFIG
  • 从 Laravel 中的命令调用控制器方法

    我有一个通过 Redis Pub Sub 监听的命令 收到发布后 我想调用控制器方法 以便可以更新数据库 但是 我无法找到任何关于如何从项目内部但在路由外部调用带有参数的控制器方法的解决方案 我见过的最接近的东西是这样的 return re
  • Redis 与 SQL Server 性能对比

    应用程序性能是使用缓存而不是关系数据库的主要原因之一 因为它以键值对的形式将数据存储在内存中 所以我们可以将经常访问的不经常更改的数据存储在缓存中 从缓存中读取比从数据库中读取要快得多 Redis 是分布式缓存市场上最好的解决方案之一 我正
  • 设置“slave-read-only no”是否会使从站确认与主站的每个哈希查找?

    我想配置从站以启用写入 slave read only no 用例是启用临时缓存 然而 文档中的这一段让我感到担忧 通常 从节点会将客户端重定向到给定命令中涉及的哈希槽的权威主节点 但是客户端可以使用从节点 以便使用 READONLY 命令

随机推荐

  • Java 8: 从永久代(PermGen)到元空间(Metaspace)

    永久代 PermGen 和元空间 Metaspace 的今世前缘 原文链接 原文作者 Monica Beckwith 以下为本人翻译 仅用于交流学习 版权归原作者和InfoQ所有 转载注明出处 请不要用于商业用途 在Java虚拟机 JVM
  • Android 拨打电话

    拨打电话 跳转到拨号界面 用户手动点击拨打 param phoneNum 电话号码 public void callPhone1 String phoneNum Intent intent new Intent Intent ACTION
  • C++使用PCL注册内存以及释放

    最近测试中发现 电脑运行一定时间就会重启 检查后发现其实是内存被占满了 然后电脑就卡住 这时会有两种情况 重启 把某些程序kill掉释放内存 这个时候不一定会kill那些占很多内存的程序 然后接着查 发现其实就是处理点云的一个程序 注册了内
  • 定时任务Schedule的使用

    定时任务或者说定时调度 是系统中比较普遍的一个功能 例如数据归档 清理 数据定时同步 非实时 定时收发 流量控制等等都需要用到定时任务 常见的定时调度框架有Quartz TBSchedule等 同样 Spring自3 0版本起也增加了任务调
  • 单片机:STM32F4x HAL库软硬SPI驱动ST7735s 1.8寸LCD屏幕

    单片机 STM32F4x HAL库软硬SPI驱动ST7735s 1 8寸LCD屏幕 说明 此篇为学习记录 可能存在错误或者不足 如有问题请指出 硬件环境 主控芯片 STM32F411CEU6 主控开发板 WeAct STM32F411CEU
  • LeetCode 817. 链表组件

    题目链接 https leetcode cn problems linked list components C 代码如下 Definition for singly linked list struct ListNode int val
  • ubuntu16.04中安装NFS服务器

    一 宿主机中对NFS服务的支持 安装相关软件 sudo apt get install nfs kernel server sudo apt get install nfs common 配置NFS服务器 编辑exports sudo vi
  • 数据结构与算法(五):优先队列

    一 基本概念 二 基于数组实现的优先队列 1 基于有序数组的实现 2 基于无序数组的实现 三 基于堆实现的优先队列 1 堆的有序化 2 基于堆实现的优先队列 四 索引优先队列 这节总结一下优先队列的常用实现方法 一 基本概念 普通的队列是一
  • python write函数换行_Python基础知识(三)

    本章小结 学习越往后越意识到总结的重要性 特别是语法基础 东西太多 不用是真的会直接忘掉 我在总结本文的时候就发现 我当时觉得学得很好很扎实 自信不会忘记的东西 真的已经被我忘掉了 还不得不依靠百度来解决问题 这坚定了我更新公众号的决心 f
  • 电调控制直流无刷电机

    实验材料 1 直流无刷电机 A2212 10 KV 1400 2 好盈天行者电调 3 stm32c8t6核心小板 先了解一下无刷电机工作原理 https www bilibili com video av29780856 电机参数 电调参数
  • 亚洲顶级域名.Asia启动注册

    亚洲顶级域名 Asia启动注册 详情到 http ipv1 blog sohu com 64602629 html 优先注册期将于2007年10月开始 并分为三个阶段 第一阶段专为政府机构预留 Asia域名而设 第二阶段让注册商标及服务标记
  • 扎心的前端开发

    喂喂喂 那个切图的 把页面写好就发给研发工程师套模板吧 你好 切图仔 不知道你的团队如何定义前端开发 据我所知 时至今日仍然有很多团队会把前端开发归类为产品或者设计岗位 虽然身份之争多少有些无谓 但我对这种偏见还是心存芥蒂 酝酿了许久 决定
  • Linux 磁盘管理

    参考 Ubuntu 下的磁盘管理 作者 莘莘 发布时间 2021 07 11 17 51 08 网址 https blog csdn net lcx1837 article details 118633544 spm 1001 2014 3
  • Docker之docker镜像容器文件拷贝到宿主主机

    上一篇 Docker之主机拷贝文件到docker镜像容器 介绍了怎么把主机上的文件拷贝到docker容器中 那么如果项目运行之后产生的日志文件 我们希望可以本地查看 那么就需要把产生的日志文件copy到我们本地机器上 来看看具体操作吧 这里
  • Linux运维笔记----时间和时区

    时间和时区 1 系统时间同步 1 1确定时间源地址 同步机IP 222 24 14 61 可以用date命令修改时间 被同步机IP 222 24 14 95 1 2确定客户主机使用的时间同步服务 chronyd service 1 3在ch
  • 多益网络java面试,java全栈工程师面试题

    前言 继续总结吧 没有面试就继续夯实自己的基础 前阵子的在面试过程中遇到的各种问题陆陆续续都会总结出来分享给大家 这次要说的也是面试中被问到的一个高频的问题 我当时其实没答好 因为很早之前是看过springboot启动过程的源码 但是时间隔
  • 以太坊智能合约之如何执行智能合约?

    区块链技术在顶级技术中占据主导地位的主要原因在于其去中心化 虽然区块链的主要目的是在没有中心的情况下维护交易记录 但为了实现自动化 智能合约被引入 那么在写完智能合约之后呢 在本文的这个以太坊智能合约教程中 我们将了解如何使用Truffle
  • 管理信息系统复习总结(保姆级)

    管理信息系统 题型 填空 单选 双选 名词解释 综合 简答 第一章 当今全球商业中的信息系统 1 管理信息系统的新变化 信息技术创新 新的业务模式 电子商务扩张 管理变革 公司和组织变革 2 信息系统如何改变企业 新兴移动数字平台 利用信息
  • Cas服务端5.3.2之开启审计功能(MySQL8)

    1 在cas overlay template的pom里面增加对cas server support audit jdbc的依赖
  • Redis缓存击穿

    什么是缓存击穿 在谈论缓存击穿之前 我们先来回忆下从缓存中加载数据的逻辑 如下图所示 因此 如果黑客每次故意查询一个在缓存内必然不存在的数据 导致每次请求都要去存储层去查询 这样缓存就失去了意义 如果在大流量下数据库可能挂掉 这就是缓存击穿