当前位置: 首页 > news >正文

Redis缓存配置

redis缓存

使用redis缓存的原因是因为在可能的高并发环境下,mysql数据库无法承受大量的请求,可能会导致数据库崩溃。而这些请求很大一部分都是查询请求,因此采用redis这样的以内存作为存储数据空间的数据库来存储查询请求的数据,这样既提高了查询的效率,又分担了一大部分请求并发的压力。

具体实现使用了AOP面向切面编程的思想,即使用@PointCut注解指定切面,在本项目中就是指定各类控制器中的各种方法,并且以**@Around注解代表的环绕方式**进行切入。利用简单类名加方法名加参数名作为缓存的键cacheKey,方法得到的值作为键的值。对于一个查询请求类型的方法执行前先在redis中查询cacheKey,如果没有则在redis中设置这个cacheKey和值以及过期时间,设置过期时间的原因是因为redis是基于内存存储数据,因此需要定期清理数据。如果有cacheKey则直接从redis中获取值。对于除了查询类型以外的方法,则先删除当前控制器下的所有缓存键,避免缓存无法更新的问题,然后再去数据库进行查询,并返回结果。

在这个过程中,可能会遇到缓存击穿,缓存穿透,缓存雪崩的问题。

因为所谓的缓存击穿,就是一个承受着高并发请求的键,因为过期时间到了,cacheKey就失效了,这时候高并发的请求会涌入mysql数据库,就像大量的请求击穿了缓存一样,因此叫做缓存击穿嘛。在本项目中解决的方案为当cacheKey不存在时,进入数据库中查询的行为使用syncronize锁住,并且使用双检锁确保只有一个线程能进入数据库查询,这样即使高并发请求的key失效也不会造成大量的请求涌入mysql数据库了,就解决了缓存击穿的问题。但是因为此项目没有高并发的用户访问,属于内部操作管理系统,所以使用syncronize已足够。

而面向高并发用户的系统使用syncronize来锁就不能解决分布式服务器的问题,因为syncronize和lock只能锁住本地JVM的线程,无法锁住其他服务器中的线程,因此在那种情况下应该采用分布式锁,而分布式锁的实现比较常见的有两种方案,一种为zookeeper实现,一种是redis实现,实现原理为请求访问时使用SETNX命令向redis中存储一个具有过期时间的key,如果成功设置则上锁成功,如果设置失败则进入阻塞。由于分布式服务器使用的是同一个redis数据库,所以就能达到互斥的效果。

而对于缓存雪崩的问题,由于缓存雪崩就是多个key同时过期导致大量查询请求进入数据库,所以在设置键的过期时间的时候精确到微妙或纳秒级别,这样就可以避免大量键同时过期的问题。

对于缓存穿透的问题,也就是解决用户利用查询数据库不存在的数据进行恶意攻击的行为,本项目中是即使数据库中查出数据可能为空,但是也添加到缓存中,这样再次查询就会查出缓存中的空而不是进入mysql数据库中查。

@Aspect
@Component
public class RedisCacheAspect {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private final Random random = new Random();//    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")@Pointcut("execution(* com.stt.cms.*.controller..*(..))")private void pointcut(){}@Around("pointcut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {Signature signature = pjp.getSignature(); //获取连接点的签名MethodSignature ms = (MethodSignature) signature;//因为在spring aop中,连接点的类型都是方法,因此,这个签名就是一个方法的签名Method method = ms.getMethod();//从方法签名中获取方法String className = method.getDeclaringClass().getSimpleName();if (method.isAnnotationPresent(GetMapping.class)) {//如果方法上存在GetMapping注解,说明这个方法就是一个查询方法// 检查是否是上传或下载方法if (method.getName().startsWith("upload") || method.getName().startsWith("download")) {return pjp.proceed(); // 直接执行方法,不进行缓存}//需要将返回结果放入redis缓存中,方便下一次查询的时候使用Object[] args = pjp.getArgs();String methodName = method.getName();String cacheKey = className + "::" + methodName + JSON.toJSONString(args);//首先去校验缓存中是否存在我们需要获取的数据if (Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey))) {//如果缓存中存在,那么就说明获取的数据就是返回值return redisTemplate.opsForValue().get(cacheKey);} else {//考虑到并发问题,这里需要上锁进行处理synchronized (this){ //这里的双检锁就是为了解决缓存击穿问题的//如果redis中不存在缓存if(Boolean.FALSE.equals(redisTemplate.hasKey(cacheKey))){//缓存中没有数据Object result = pjp.proceed(); //执行方法得到的返回结果//这里的过期时间要设置为随机过期时间,防止缓存雪崩,但是需要注意的是,随机时间是小单位的//随机,不能是大单位的随机long expire = Duration.ofMinutes(5).toNanos() + random.nextInt(1000);redisTemplate.opsForValue().set(cacheKey, result, expire, TimeUnit.NANOSECONDS);return result;}  else {return redisTemplate.opsForValue().get(cacheKey);}}}//这里还需要考虑缓击穿和缓存存穿透问题} else {//其余情况我们只需要考虑增删改带来的缓存失效问题的处理if(method.isAnnotationPresent(PostMapping.class)|| method.isAnnotationPresent(PutMapping.class)||method.isAnnotationPresent(DeleteMapping.class)){//这里需要考虑去删除缓存,从而更新缓存List<String> cacheKeys = redisTemplate.execute((RedisCallback<List<String>>) connection -> {ScanOptions scanOptions = ScanOptions.scanOptions().match(className + "*").count(50).build();List<String> keys = new ArrayList<>();Cursor<byte[]> cursor = connection.scan(scanOptions);while (cursor.hasNext()) {byte[] next = cursor.next();String scanKey = new String(next);System.err.println("扫描到失效的键:" + scanKey);keys.add(scanKey);}return keys;});if(cacheKeys != null)redisTemplate.delete(cacheKeys);}return pjp.proceed();}}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 大数据治理平台建设与应用解决方案(41页PPT)
  • JAVA解压文件到目标目录
  • Nginx系列-负载均衡
  • 思科静态路由配置1
  • 实训日记day27
  • 热搜|“月薪4300一个月的存钱计划”,普通人如何实现财富自由?
  • C:每日一题:单身狗
  • 汇昌联信做拼多多店铺如何运营?
  • 微信小程序 for,if语法 事件对象,事件传参
  • 岗位信息采集全攻略:两种方法快速获取招聘信息
  • Mariadb数据库本机无密码登录的问题解决
  • Go语言排序艺术:sort包的精妙运用
  • 数据集与数据库:有什么区别?
  • C++ 之动手写 Reactor 服务器模型(一):网络编程基础复习总结
  • 浅谈C语言位段
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • 【剑指offer】让抽象问题具体化
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • Javascripit类型转换比较那点事儿,双等号(==)
  • Javascript设计模式学习之Observer(观察者)模式
  • java概述
  • Java小白进阶笔记(3)-初级面向对象
  • leetcode46 Permutation 排列组合
  • Linux中的硬链接与软链接
  • PAT A1120
  • SpringBoot 实战 (三) | 配置文件详解
  • Vue.js 移动端适配之 vw 解决方案
  • 从输入URL到页面加载发生了什么
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 计算机在识别图像时“看到”了什么?
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 使用Gradle第一次构建Java程序
  • 为视图添加丝滑的水波纹
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 一、python与pycharm的安装
  • 智能合约Solidity教程-事件和日志(一)
  • linux 淘宝开源监控工具tsar
  • Linux权限管理(week1_day5)--技术流ken
  • 阿里云移动端播放器高级功能介绍
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • ​queue --- 一个同步的队列类​
  • # Apache SeaTunnel 究竟是什么?
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • #HarmonyOS:Web组件的使用
  • #if等命令的学习
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #考研#计算机文化知识1(局域网及网络互联)
  • #知识分享#笔记#学习方法
  • $(this) 和 this 关键字在 jQuery 中有何不同?
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六