Redis缓存穿透的几种解决方案

目录

缓存穿透原理:

缓存穿透一般有几种解决方案:

1.缓存空值

2.使用锁

3.布隆过滤器

 优缺点

布隆过滤器误判理解

布隆过滤器的简单使用流程

4.组合方案

那么当我们高并发的访问短链接或者人为的去穿透的时候呢?


最近做项目遇到了缓存的一些问题,总结一下解决方法

缓存穿透原理:

缓存穿透是指在缓存中查询一个一定不存在的数据,由于缓存不命中,导致请求直接访问数据库,这将导致大量的请求打到数据库上,可能会导致数据库压力过大。

具体原理:

缓存穿透一般有几种解决方案:
1.缓存空值

当查询结果为空时,也将结果进行缓存,但是设置一个较短的过期时间。这样在接下来的一段时间内,如果再次请求相同的数据,就可以直接从缓存中获取,而不是再次访问数据库。

这种方式是比较简单的一种实现方案,可以很好解决缓存穿透问题,但是会存在一些弊端。那就是当短时间内存在大量恶意请求,缓存系统会存在大量的内存占用。如果要解决这种海量恶意请求带来的内存占用问题,需要搭配一套风控系统,对用户请求缓存不存在数据进行统计,进而封禁用户。整体设计就较为复杂,不推荐使用

2.使用锁

当请求发现缓存不存在时,可以使用锁机制来避免多个相同的请求同时访问数据库,只让一个请求去加载数据,其他请求等待。

这种方式可以解决数据库压力过大问题,如果会出现“误杀”现象,那就是如果缓存中不存在但是数据库存在这种情况,也会等待获取锁,用户等待时间过长,不推荐使用

3.布隆过滤器

布隆过滤器是一种数据结构,用于快速判断一个元素是否存在于一个集合中。具体来说,布隆过滤器包含一个位数组和一组哈希函数。位数组的初始值全部置为 0。在插入一个元素时,将该元素经过多个哈希函数映射到位数组上的多个位置,并将这些位置的值置为 1。

查询一个元素是否存在时,会将该元素经过多个哈希函数映射到位数组上的多个位置,如果所有位置的值都为 1,则认为元素存在;如果存在任一位置的值为 0,则认为元素不存在。

 优缺点

优点:

  • 高效地判断一个元素是否属于一个大规模集合。
  • 节省内存。

缺点:

  • 可能存在一定的误判。
布隆过滤器误判理解
  • 布隆过滤器要设置初始容量。容量设置越大,冲突几率越低。
  • 布隆过滤器会设置预期的误判值。
布隆过滤器的特点
  • 查询是否存在,如果返回存在,可能数据是不存在的。
  • 查询是否存在,如果返回不存在,数据一定不存在。

布隆过滤器的简单使用流程

1.导入依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId>
</dependency>

2.配置Redis参数

spring:data:redis:host: 127.0.0.1port: 6379password: 123456

3.配置布隆过滤器:

import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 布隆过滤器配置*/
@Configuration
public class RBloomFilterConfiguration {/*** 防止用户注册查询数据库的布隆过滤器*/@Beanpublic RBloomFilter<String> userRegisterCachePenetrationBloomFilter(RedissonClient redissonClient) {RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter("userRegisterCachePenetrationBloomFilter");cachePenetrationBloomFilter.tryInit(10000000L, 0.001);return cachePenetrationBloomFilter;}
}

注意:

tryInit 有两个核心参数:

  • expectedInsertions:预估布隆过滤器存储的元素长度。
  • falseProbability:运行的误判率。

错误率越低,位数组越长,布隆过滤器的内存占用越大。

错误率越低,散列 Hash 函数越多,计算耗时较长。

4.使用:

4.1先注入:private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

4.2

userRegisterCachePenetrationBloomFilter.contains(xxx) 这个判断元素是否存在布隆过滤器

userRegisterCachePenetrationBloomFilter.add(xxx)  把元素加入布隆过滤器

4.组合方案

上面的这些方案或多或少都会有些问题,应该用三者进行组合用来解决缓存穿透问题。如果说缓存不存在,那么就通过布隆过滤器进行初步筛选,然后判断是否存在缓存空值,如果存在直接返回失败。如果不存在缓存空值,使用锁机制避免多个相同请求同时访问数据库。最后,如果请求数据库为空,那么将为空的 Key 进行空对象值缓存

1.当我们生成短链接的时候,因为完整短链接是唯一的,我们用布隆过滤器判断,不存在才生成。

​
​private String generateSuffix(ShortLinkCreateReqDTO shortLinkCreateReqDTO) {int count = 0;String shortUri;while (true) {if (count > 10) {throw new ServiceException("短链接创造频繁,请稍后再试!");}//加上当前毫秒数,减少重复可能shortUri = HashUtil.hashToBase62(shortLinkCreateReqDTO.getOriginUrl() + System.currentTimeMillis());if (!shortUriCreateCachePenetrationBloomFilter.contains(shortLinkCreateReqDTO.getDomain() + "/" + shortUri)) {break;}count++;}return shortUri;}​​

2.当上一步的布隆过滤器误判了,明明存在但判断不存在。当我们插入短链接的时候,去查一次数据库。如果存在数据,证明布隆过滤器误判。

        try {baseMapper.insert(shortLinkDO);shortLinkGotoMapper.insert(shortLinkGotoDO);//   basemapper的插入: 这个异常是 插入mysql 是 key重复了 因为布隆过滤器误判才会如此//    存在的 判断为 不存在} catch (DuplicateKeyException ex) {// TODO 布隆过滤器误判咋办// 那就去数据库查在判断一次ShortLinkDO ifExit = this.baseMapper.selectOne(Wrappers.lambdaQuery(ShortLinkDO.class).eq(ShortLinkDO::getFullShortUrl, full_short_link));if (ifExit != null) {log.warn("短链接:{} 重复入库", full_short_link);throw new ServiceException("短链接存在了!!");}}

3.成功插入之后,把完整短链接加入布隆过滤器。同时缓存预热,key为原始连接

        stringRedisTemplate.opsForValue().set(String.format(GOTO_SHORT_LINK_KEY,full_short_link),shortLinkCreateReqDTO.getOriginUrl(),LinkUtil.getLinCacheValidDate(shortLinkCreateReqDTO.getValidDate()),TimeUnit.MILLISECONDS);shortUriCreateCachePenetrationBloomFilter.add(full_short_link);

以上是插入一条短链接的判断大致流程


那么当我们高并发的访问短链接或者人为的去穿透的时候呢?

比如下面有人恶意请求毫秒级触发大量请求去一个插入的短链接

1.先从缓存中拿原始链接(这个访问当时是之前我们已经通过插入mysql时候,预热到缓存中的),拿到就跳转,(这里跳转不了)

2.布隆过滤器判断是否包含完整的短链接(明显没有,如果误判的话,逻辑下走)

3.这个要调回头再看,第一次明显不走

4.分布式锁,双检加锁策略。

5.因为数据本就不存在,所以shortLinkGotoDO == null,存入信息到,IS_NO_SHORK_LINK,对应了第3步

以上步骤,我们判断了布隆过滤器,查看了是否为空值,加了分布式锁。

6.一直向向下乃至释放锁是正常访问的流程。

    @SneakyThrows@Overridepublic void restoreUrl(String shortUri, ServletRequest request, ServletResponse response) {String serverName = request.getServerName();String full_short_url = serverName + "/" + shortUri;//1.先从缓存中那 跳转的原始链接 拿到的话直接跳转String origin_url = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, full_short_url));if (StrUtil.isNotBlank(origin_url)) {HttpServletResponse response1 = (HttpServletResponse) response;response1.sendRedirect(origin_url);return;}// 判断布隆过滤器是否存在 完整短连接, 这个full_short_url 在添加短连接的时候就添加到布隆过滤器里面了// 这个避免了 穿透 乱输入的链接地址 PS : 误判的话,逻辑向下走 通过redis的路由表 在判断一次boolean contains = shortUriCreateCachePenetrationBloomFilter.contains(full_short_url);if(!contains) {((HttpServletResponse) response).sendRedirect("/page/notfound");return;}//缓存的路由信息 存在String isNoShortGotoLink = stringRedisTemplate.opsForValue().get(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, full_short_url));if(StrUtil.isNotBlank(isNoShortGotoLink)) {((HttpServletResponse) response).sendRedirect("/page/notfound");return;}//分布式锁RLock lock = redissonClient.getLock(String.format(LOCK_GOTO_SHORT_LINK_KEY, full_short_url));lock.lock();try {origin_url = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, full_short_url));if(StrUtil.isNotBlank(origin_url)) {HttpServletResponse response1 = (HttpServletResponse) response;response1.sendRedirect(origin_url);return;}//根据 full_short_url 查找路由表ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(Wrappers.lambdaQuery(ShortLinkGotoDO.class).eq(ShortLinkGotoDO::getFullShortUrl, full_short_url));if (shortLinkGotoDO == null) {//    这个旨在解决布隆过滤器误判stringRedisTemplate.opsForValue().set(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, full_short_url),"-",30, TimeUnit.MINUTES);((HttpServletResponse) response).sendRedirect("/page/notfound");return;}//根据路由表的Gid 和 full_short_url 查找 shortLinkDOShortLinkDO shortLinkDO = baseMapper.selectOne(Wrappers.lambdaQuery(ShortLinkDO.class).eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid()).eq(ShortLinkDO::getEnableStatus, 0).eq(ShortLinkDO::getDelFlag, 0).eq(ShortLinkDO::getFullShortUrl, shortLinkGotoDO.getFullShortUrl()));if (shortLinkDO != null) {//这是解决短链接 已经过期的问题if(shortLinkDO.getValidDate() !=null && shortLinkDO.getValidDate().before(new Date())) {stringRedisTemplate.opsForValue().set(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, full_short_url),"-",30, TimeUnit.MINUTES);((HttpServletResponse) response).sendRedirect("/page/notfound");return;}stringRedisTemplate.opsForValue().set(String.format(GOTO_SHORT_LINK_KEY, full_short_url),shortLinkDO.getOriginUrl(),LinkUtil.getLinCacheValidDate(shortLinkDO.getValidDate()),TimeUnit.MILLISECONDS);HttpServletResponse response1 = (HttpServletResponse) response;response1.sendRedirect(shortLinkDO.getOriginUrl());}} finally {lock.unlock();}}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

淘宝API接口开发系列——淘宝详情数据采集

淘宝详情数据采集涉及多种技术和方法&#xff0c;下面列举几种常见的方式&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 爬虫技术&#xff1a;使用编程语言&#xff08;如Python&#xff09;编写网络爬虫程序&#xff0c;通过模拟浏览器行为访问淘宝网站&#xff0c;…

atoi函数及模拟实现

✅博客主页:爆打维c-CSDN博客​​​​​​ &#x1f43e; &#x1f539;分享c语言知识及代码 文章目录 一、atoi函数的简要介绍 1.函数原型 二、atoi函数的使用示例 &#x1f4a0;正数示例&#xff1a; &#x1f4a0;负数示例&#xff1a; &#x1f4a0;带有符号示例&am…

【C语言步行梯】C语言实现扫雷游戏(含详细分析)

&#x1f3af;每日努力一点点&#xff0c;技术进步看得见 &#x1f3e0;专栏介绍&#xff1a;【C语言步行梯】专栏用于介绍C语言相关内容&#xff0c;每篇文章将通过图片代码片段网络相关题目的方式编写&#xff0c;欢迎订阅~~ 文章目录 需求分析具体实现主函数体菜单实现游戏实…

Docker 从0安装 nacos集群

前提条件 Docker支持一下的CentOs版本 Centos7(64-bit)&#xff0c;系统内核版本为 3.10 以上Centos6.5(64-bit) 或者更高版本&#xff0c;系统内核版本为 2.6.32-431 或者更高版本 安装步骤 使用 yum 安装&#xff08;CentOS 7下&#xff09; 通过 uname -r 命令查看你当…

MFC界面美化第四篇----自绘list列表(重绘列表)

1.前言 最近发现读者对我的mfc美化的专栏比较感兴趣&#xff0c;因此在这里进行续写&#xff0c;这里我会计划写几个连续的篇章&#xff0c;包括对MFC按钮的美化&#xff0c;菜单栏的美化&#xff0c;标题栏的美化&#xff0c;list列表的美化&#xff0c;直到最后形成一个完整…

Codeforces Round 935 (Div. 3)(A,B,C,D,E,F)

比赛链接 阳间场&#xff0c;阴间题&#xff0c;最考阅读理解的一场。题目本身的难度不大。 A. Setting up Camp 题意&#xff1a; 组委会计划在奥运会结束后带领参赛者进行一次徒步旅行。目前&#xff0c;需要携带的帐篷数量正在计算中。据了解&#xff0c;每个帐篷最多可容…

前端vue3-手动设置滚动条位置/自动定位

从B页面进行xx操作后需要跳转到A页面&#xff0c;并定位到AA职位&#xff0c;上图为A页面。 A页面的左侧是div&#xff0c;内层包裹List组件 给div定义refleftRef,在代码中写如下&#xff1a; function scrollTop() {if (leftRef.value) {console.log(99, leftRef.value);next…

0基础 三个月掌握C语言(13)

数据在内存中的存储 整数在内存中的存储 在讲解操作符时 我们就已经学习了该部分的内容 这里我们回顾一下 整数的二进制表示方法有三种&#xff1a;原码 反码 补码 有符号的整数&#xff08;unsigned&#xff09; 三种表达方式均有符号位和数值位两部分 最高位的一位被当…

文件包含漏洞之包含SESSION(CTF题目)

这次使用的环境是ubuntunginxphpmysql 首先四个文件源码在以下链接中&#xff1a; 一道CTF题&#xff1a;PHP文件包含 | Chybeta 我们注册一个用户名111密码111&#xff0c;然后登录查看cookie和linux的session&#xff0c;因为我们的de服务器 是手动搭建的&#xff0c;所以…

Java IO模型

NIO Java IO 模型1. 什么是IO计算机结构角度应用程序角度 2. 常见的内存模型3. Java中常见的IO模型3.1 BIO&#xff08;Blocking I/O&#xff09;3.2 NIO&#xff08;Non-blocking/New I/O&#xff09;同步非阻塞 IO 模型I/O 多路复用模型 3.3 AIO&#xff08;Asynchronous I/O…

Spring6.1新特性,四种方式调用REST接口(RestClient、WebClient、RestTemplate、HTTP Interface)

个人博客&#xff1a;无奈何杨&#xff08;wnhyang&#xff09; 个人语雀&#xff1a;wnhyang 共享语雀&#xff1a;在线知识共享 Github&#xff1a;wnhyang - Overview 官网 REST Clients :: Spring Framework The Spring Framework provides the following choices for…

电子元器件批发采购中的供应链透明度与可追溯性

电子元器件批发采购中的供应链透明度与可追溯性是非常重要的&#xff0c;特别是考虑到供应链的复杂性和全球化。以下是一些关于如何增强供应链透明度和可追溯性的建议&#xff1a; 供应商审核与选择&#xff1a;对潜在的供应商进行全面的审核和评估&#xff0c;了解其供应链结构…

【Leetcode】1793. 好子数组的最大分数

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个整数数组 n u m s nums nums &#xff08;下标从 0 0 0 开始&#xff09;和一个整数 k k k 。 一个子数组 ( i , j ) (i, j) (i,j) 的 分数 定义为 m i n ( n u m s …

ROS2从入门到精通0-3:VSCode 搭建 ROS2 工程环境

目录 0 专栏介绍1 Ubuntu下安装VSCode1.1 基本安装1.2 将VSCode添加到侧边栏 2 VSCode集成相关插件3 VSCode运行ROS2环境步骤3.1 安装编译依赖项3.2 创建工作空间和源码空间3.3 启动VSCode与配置 4 测试工程环境4.1 C版本4.2 Python版本 0 专栏介绍 本专栏旨在通过对ROS2的系统…

一、初识 web3

瑾以此系列文章&#xff0c;献给那些出于好奇并且想要学习这方面知识的开发者们 在多数时间里&#xff0c;我们对 web3 的理解是非常模糊的 就好比提及什么是 web1 以及 web2&#xff0c;相关概念的解释是&#xff1a; 1. 从 Web3 的开始 Web3&#xff0c;也被称为Web3.0&…

idea error java:compilation failed:internal java compiler error

idea中编译运行maven项目报错如下 idea error java:compilation failed:internal java compiler error 尝试如下操作 注意&#xff1a;jdk8 需要设置4个地方 1.首先打开File->Project Structure中的Project&#xff0c;将SDK和language level都设置一致&#xff0c;如下…

基于Java的考研专业课程管理系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高校教师管理模块2.4 考研专业模块2.5 考研政策模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 考研高校表3.2.2 高校教师表3.2.3 考研专业表3.2.4 考研政策表 四、系统展示五、核…

文件操作:文本文件(写/读)

文件操作可以将数据永久化&#xff0c;C中对文件操作需要包含头文件 < fstream > 文件类型分为两种&#xff1a; 1. 文本文件&#xff1a;文件以文本的ASCII码形式存储在计算机中 2. 二进制文件&#xff1a;文件以文本的二进制形式存储在计算机中&#xff0c;…

matplotlib绘制统计特征图和分布特征图

文章目录 一、统计特征图绘制1.需求2.代码方法一方法二总结 二、分布特征图绘制1.需求2.代码 一、统计特征图绘制 1.需求 我现在有两个数据集Pdata和Cdata分别在DataFrame对象中&#xff0c;我现在想对这两个数据集进行统计特征分析&#xff0c;并用直方图展示出来。 2.代码…

CSS学习(3)-浮动和定位

一、浮动 1. 元素浮动后的特点 脱离文档流。不管浮动前是什么元素&#xff0c;浮动后&#xff1a;默认宽与高都是被内容撑开&#xff08;尽可能小&#xff09;&#xff0c;而且可以设置宽 高。不会独占一行&#xff0c;可以与其他元素共用一行。不会 margin 合并&#xff0c;…