Redis缓存击穿和穿透以及雪崩

  • 缓存穿透
    • 缓存穿透问题解决方案
      • 缓存空对象
      • 布隆过滤器
      • redisson 实现布隆过滤器
        • 引入依赖
        • 预先放入数据
        • 使用
  • 缓存击穿
  • 缓存雪崩
    • 预防和解决方案
  • 热点key的重建优化

缓存穿透

缓存穿透指的是查询一个根本不存在的数据,缓存层和存储层都不会命中,通常处于容错的考虑,如果存储层不存在数据也不会写入缓存层。这样就导致每次请求都要去存储层查询,失去了缓存保护后端存储的意义。

造成缓存穿透的原因有两个:

  1. 自身业务代码或者数据出现问题
  2. 一些恶意攻击,爬虫等造成大量的空命中

缓存穿透问题解决方案

缓存空对象

String get(String key) {
    // 从缓存中获取数据
    String cacheValue = cache.get(key);
    // 缓存为空
    if (StringUtils.isBlank(cacheValue)) {
        // 从存储中获取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        // 如果存储数据为空, 需要设置一个过期时间(300秒)
        if (storageValue == null) {
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    } else {
        // 缓存非空
        return cacheValue;
    }
}

布隆过滤器

对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透可以先用布隆过滤器做一次筛选,对于不存在数据,布隆过滤器一般都能够过滤掉,不让请求再继续往后端走。

布隆过滤器的特性是:当它说这个值存在,那么这个值可能存在,也可能不存在;当它说不存在时,这个值一定不存在

它的这种特性是由它的算法和数据结构决定的:

如上图所示,布隆过滤器就是一个大型的位数组和几个不一样的无偏hash函数,所谓无偏,就是能够把元素的hash值计算的比较均匀。工作原理如下:
向布隆过滤器添加key时,会使用多个hash函数对key进行hash运算,得出一个整数索引值,然后对数组长度进行取模运算,得到在数组中的位置。每个hash函数都会算的一个不同的位置,再把为位数组的这几个位置都置为1,就完成了add操作

那么为什么需要多个hash函数呢?先带着问题看一下布隆过滤器如何判断一个值是否存在:
当检查一个元素是否在布隆过滤器中时,同样会将该元素哈希成多个不同的位数组下标,然后检查这些下标对应的位是否都为1。如果有任何一个位为0,则可以确定该元素一定不存在于布隆过滤器中;如果所有位都为1,则该元素可能存在于布隆过滤器中,但也可能是一个假阳性(false positive)。

设置多个hash函数主要是为了减少假阳性的概率。当只使用一个哈希函数时,会有可能出现多个元素被哈希到同一个位数组下标的情况,从而导致误判。而使用多个哈希函数可以让元素分布在更多的位数组下标上,从而减少假阳性的概率。当使用k个哈希函数时,假阳性的概率为(1 - \frac{1}{m})^{kn},其中m是位数组的长度,n是元素数量。
因此,使用多个哈希函数可以提高布隆过滤器的准确性和可靠性。

综上述可以得知,布隆过滤器的可靠性大致由 hash 函数的个数,以及位数组的长度决定。hash 函数的个数计算有些复杂,暂按下不表。而位数组的长度一般我们不能直接设定,而是先设置成业务中待查找样本的总量,布隆过滤器会自动调整位数组的长度。这个长度一般很大,过亿都是正常的,因为它是按位存储的,一亿的长度所占的空间也就 12M。

这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为复杂, 但是缓存空间占用很少。

redisson 实现布隆过滤器

引入依赖
dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.6.5</version>
</dependency>
预先放入数据

使用布隆过滤器需要把所有数据提前放入布隆过滤器,并且在增加数据时也要往布隆过滤器里放,但是布隆过滤器不支持删除数据,如果原始数据删除比较多,需要定期重置布隆过滤器,以保持较高的正确性

/初始化布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
        
//把所有数据存入布隆过滤器
void init(){
    keys = ["zhangsan","lisi","wanger" ....]
    for (String key: keys) {
        bloomFilter.put(key);
    }
}
使用
public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小
        bloomFilter.tryInit(100000000L,0.03);

        //判断下面号码是否在布隆过滤器中
        System.out.println(bloomFilter.contains("sa"));//false
        System.out.println(bloomFilter.contains("jlsd"));//false
        System.out.println(bloomFilter.contains("zhangsan"));//true
    }
}

缓存击穿

由于大批量缓存在同一时间失效可能导致大量得请求同时穿透缓存直达访问数据库,可能会造成数据库瞬间压力过大甚至挂掉。为应对这种情况,我们一般将某批缓存在过期的时间基础上再加一个短暂的随机值,避免同时失效, 伪代码如下:

String get(String key) {
    // 从缓存中获取数据
    String cacheValue = cache.get(key);
    // 缓存为空
    if (StringUtils.isBlank(cacheValue)) {
        // 从存储中获取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        //设置一个过期时间(300到600之间的一个随机数)
        int expireTime = new Random().nextInt(300)  + 300;
        if (storageValue == null) {
            cache.expire(key, expireTime);
        }
        return storageValue;
    } else {
        // 缓存非空
        return cacheValue;
    }
}

缓存雪崩

缓存雪崩指的是缓存层支撑不住请求或者宕机了,流量会直接冲击到后端存储层。

由于缓存层承载这大量的请求,有效的保护了存储层,但是如果某些原因导致缓存层不能提供正常的服务,如:

  1. 超大并发,缓存层支撑不住
  2. 缓存设计不好,类似于大量请求 bigkey,导致缓存层服务性能急剧下降
    这样就导致大量的请求都会打到后端,造成存储层宕机

预防和解决方案

  1. 保证缓存服务的高可用,使用哨兵架构,或者集群架构
  2. 依赖隔离组件为后端请求限流熔断并降级,比如使用 sentinel 或者 Hystrix 限流降级组件

    比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。

  3. 提前演练。在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基础上做一些预案设定。

热点key的重建优化

对于缓存的使用一般是 "缓存 + 过期时间"的方式,既能提高接口的并发,也能在一定的时间内保证数据最新。这种方式基本能满足大部分需求,但是下面两个问题同时出现时,可能会对应用造成致命的伤害:

  1. 当前key热度很高,并发量很大
  2. 重建缓存的过程比较耗时

这种情况下缓存失效的瞬间,有大量的线程去重建缓存,造成后端负载过大,甚至导致宕机。解决这个问题的关键是要避免大量线程同时重建缓存:

可以使用互斥锁来解决,此方法只允许一个线程重建缓存,其他线程等待,然后重新获取最新数据,伪代码如下:

String get(String key) {
    // 从Redis中获取数据
    String value = redis.get(key);
    // 如果value为空, 则开始重构缓存
    if (value == null) {
        // 只允许一个线程重建缓存, 使用nx, 并设置过期时间ex
        String mutexKey = "mutext:key:" + key;
        if (redis.set(mutexKey, "1", "ex 180", "nx")) {
             // 从数据源获取数据
            value = db.get(key);
            // 回写Redis, 并设置过期时间
            redis.setex(key, timeout, value);
            // 删除key_mutex
            redis.delete(mutexKey);
        }// 其他线程休息50毫秒后重试
        else {
            Thread.sleep(50);
            get(key);
        }
    }
    return value;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/5011.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shell echo 命令

文章目录Shell echo命令1.显示普通字符串:2.显示转义字符3.显示变量4.显示换行5.显示不换行6.显示结果定向至文件7.原样输出字符串&#xff0c;不进行转义或取变量(用单引号)8.显示命令执行结果Shell echo命令 Shell 的 echo 指令与 PHP 的 echo 指令类似&#xff0c;都是用于字…

@PostConstruct注解

PostConstruct注解 定义&#xff1a; PostConstruct是Java自带的注解&#xff0c;在方法上加该注解会在项目启动的时候执行该方法&#xff0c;也可以理解为在spring容器初始化的时候执行该方法。 从Java EE5规范开始&#xff0c;Servlet中增加了两个影响Servlet生命周期的注解…

精准水位在流批一体数据仓库的探索和实践

作者 | 浮生若梦的石头 导读 随着实时计算技术在大数据中的广泛应用&#xff0c;数据的时效性得到大幅度&#xff0c;但是实际应用场景中&#xff0c;除了时效性&#xff0c;还面临着更高的技术要求。 本文结合实时计算的水位技术在流批一体数据仓库中的探索和实践&#xff0c;…

elementUI使用

一&#xff1a;elementUI下拉框错位 下图为错位示例 使用如下方法&#xff0c;可解决错位问题 // 在模板文件中&#xff0c;配置不让组件插入body中 <el-select:popper-append-to-body"false"> // 使用决定定位&#xff0c;强制下拉选项放在下拉框下方且对齐。…

一键卸载流氓垃圾软件,这2款软件让电脑干净无弹窗

​你是否因为不知道怎么卸载流氓垃圾软件而崩溃&#xff1f;你是否在为不知从何而来的广告弹窗而抓狂&#xff1f;很多小伙伴表示&#xff0c;电脑联网就疯狂弹窗&#xff0c;还莫名其妙下载了很多软件。今天教你一键卸载流氓垃圾软件&#xff0c;告别弹窗&#xff0c;还你一个…

2.5 数据部分总结

2.5 数据部分总结 李沐 B站&#xff1a;https://space.bilibili.com/1567748478/channel/collectiondetail?sid28144 课程主页&#xff1a;https://c.d2l.ai/stanford-cs329p/ 1. 数据方面的挑战&#xff1a; 数据量和标注质量的权衡&#xff1a; ​ 在数据方面也会有挑战&a…

3月31号 上午 数据结构课程中 引出的几个算法题目

T1:约瑟夫游戏-- 剑指 Offer 62. 圆圈中最后剩下的数字&#xff1a; 0,1,,n-1这n个数字排成一个圆圈&#xff0c;从数字0开始&#xff0c;每次从这个圆圈里删除第m个数字&#xff08;删除后从下一个数字开始计数&#xff09;。求出这个圆圈里剩下的最后一个数字。 例如&…

[Few-shot learning] Siamese neural networks

这篇文章主要介绍的是Siamese Neural Network经典论文&#xff1a; Gregory Koch, et al., Siamese Neural Networks for One-shot Image Recognition. ICML 2015。 神经网络能够取得非常好的效果得益于使用大量的带标签数据进行有监督学习训练。但是这样的训练方法面临两个难题…

【Unity入门】资源包导入和导出

【Unity入门】资源包导入和导出 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;1&#xff09;资源目录 Unity的资源&#xff08;模型&#xff0c;场景&#xff0c;脚本&#xff09;等都保存在Assert目录下&…

Python中进程和线程到底有什么区别?

人生苦短&#xff0c;我用python python 安装包资料:点击此处跳转文末名片获取 一、进程和线程的关系 线程与进程的区别可以归纳为以下4点&#xff1a; 地址空间和其它资源&#xff08;如打开文件&#xff09;&#xff1a;进程间相互独立&#xff0c;同一进程的各线程间共享。…

2023爱分析 · 认知智能厂商全景报告 | 爱分析报告

报告编委 黄勇 爱分析合伙人&首席分析师 李进宝 爱分析高级分析师 陈元新 爱分析分析师 目录 1. 研究范围定义 2. 市场洞察 3. 厂商全景地图 4. 市场分析与厂商评估 5. 入选厂商列表 1. 研究范围定义 研究范围 人工智能的发展分为三个阶段——计算…

【C++】类和对象(中)—构造函数|析构函数|拷贝构造|赋值重载

类和对象&#xff08;中&#xff09;一、类的6个默认成员函数二、构造函数☀️构造函数概念☀️构造函数特性三、析构函数❄️析构函数的概念❄️析构函数的特性小总结四、拷贝构造&#x1f30d;拷贝构造概念&#x1f30d;拷贝构造特性五、赋值运算符重载&#x1f318;运算符重…

基于sprinmgboot实现实习管理系统的设计【源码+论文】

基于sprinmgboot实习管理系统的设计与实现演示摘要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;实习管理也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管…

环境搭建:使用python matplotlib画图不显示中文问题解决

1.背景 python matplotlib.plt 使用 plt.title 写标题时&#xff0c;标题显示为方框&#xff0c;无法正常显示中文&#xff0c;而且基本上在一台新的服务器上配置python开发环境都会遇到这种问题&#xff0c;因此写个文章记录一下。 画图不显示中文的问题如下&#xff1a; 2…

JQuery——BreakingNews.js新闻滚动效果

BreakingNews.js新闻滚动效果 这里是直观效果展示 使用这种效果我们需要三个包 jq.min.js BreakingNews.css breakingnews.js breakingnews.js部分 (function(jQuery){$.fn.BreakingNews function(settings){var defaults{background :#FFF,title :NEWS,titlecolor :#…

搜索与图论 - 搜索与图在算法中的应用【中】

目录 迪杰斯特拉算法Dijkstra Dijkstra求最短路 I Dijkstra求最短路 II 贝尔曼-福特算法 bellman-ford 有边数限制的最短路 SPFA算法 spfa求最短路 spfa判断负环 Floyd Floyd求最短路 迪杰斯特拉算法Dijkstra 该算法不能存在负权边 Dijkstra求最短路 I 思路&#xff1…

[oeasy]python0120_英语的崛起_英文字符_小写字母的由来_不列颠帝国

各语言字符编码 回忆上次内容 罗马 承袭了 希腊的文化 学习了 希腊的字符 拥有 罗马帝国的战力基督教文化的影响 这个时候 不列颠 只是 凯撒高卢战记里的 边缘小国 但是 我们现在学python用的字符 不是希腊、罗马字符而是英文字符 英国是如何从边缘之地引领文化、走向世界的…

android:手搓一个即时消息聊天框(包含消息记录)

先看一下效果 1.后端 要实现这个&#xff0c;先说一下后端要实现的接口 1.创建会话id 传入“发送id”和“接收id”给服务端&#xff0c;服务端去创建“会话id” 比如 get请求&#xff1a;http://xxxx:8110/picasso/createSession?fromUserId1&toUserId2 返回seesionId…

【新】(2023Q2模拟题JAVA)华为OD机试 - 机器人活动区域

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:机器人活动区域 题目 现有一…

分享:数据库存储与索引技术(三)LSM树实现案例

欢迎访问 OceanBase 官网获取更多信息&#xff1a;https://www.oceanbase.com/ 本文来自OceanBase社区分享&#xff0c;仅限交流探讨。原作者马伟&#xff0c;长期从事互联网广告检索系统的研发&#xff0c;对数据库&#xff0c;编译器等领域也有浓厚兴趣。 文章目录1. MemTab…
最新文章