本文来自网易云社区

本剧情纯属真实,犹如雷同实乃缘分。

发生

事情的发生在某天早上,天气怎样反正是忘了,只记得当时监控平台大量的数据库错误报警。 作为后端开发,当看到日志中大量的db连接获取失败,心情是复杂的。

看了下配置和实际连接数,竟然。。。没满。恩,可能是突发流量。然而没多久,一大波报警又袭来,感觉事情没那么简单。

常规措施无果,连续数次如此,看日志发现时间有点奇怪,都是间隔5分钟, 难道。。。缓存失效了? 看业务代码和缓存配置,很有可能。

业务代码表示

  1. @Cacheable(value = "item_volume", key = "'item_' + #gid", unless = "#result == null")
  2. public Item queryiItem(long gid) {
  3. Optional<Item> optional = itemService.getItem(null, gid);
  4. return optional.orNull();
  5. }

注: cache的实现用的是spring->

初步分析

没错,换配置item_volume过期时间5分钟,也就是说,当缓存过期后,此时如果有大量请求,那么这些请求都会因为缓存失效而请求数据库。 看起来情形是这样的:

如果假设成立,那就是spring在处理缓存的时候,如果没有命中,直接穿透执行实际操作(db查询),也就是说,中间是不加锁的。

这样就解释通了,但这是bug吗,还是spring认为是个feature, 这是个问题。

发展

Talk is cheap, show me the code. --linux之爹

关键是,code在哪。又得上套路了:套路: 既然是AOP,找找Advice。 最直接能想到,就是在spring中找所有Advice接口的继承树,然而数量太多,逐个寻找验证实在是耗时。

熟悉spring事务的同学应该能想到@Transactional的Advice是TransactionInterceptor,那么cache是否对应对一个CacheInterceptor呢。一看,还真有,那就好办了,而且看起就是要找的。

修改代码

顺着CacheInterceptor的invoke方法,定位到CacheAspectSupport.execute,看代码实现,确实没加锁,那就加个锁呗:

  1. private Lock lock = new ReentrantLock();
  2. //execute中部分代码 lock.lock();
  3. try {
  4. result = findCachedItem(contexts.get(CacheableOperation.class));
  5. if (result == null) {
  6. result = new SimpleValueWrapper(invokeOperation(invoker));
  7. }
  8. collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests);
  9. for (CachePutRequest cachePutRequest : cachePutRequests) {
  10. cachePutRequest.apply(result.get());
  11. }
  12. processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get());
  13. } finally {
  14. lock.unlock();
  15. }

其中,lock相关为新加部分。

高潮

代码是改好了,怎么生效呢。还得回头看看CacheInterceptor是如何注入的,也不难找到:

那就写个类 MyCacheAspectSupport.java.txt 来代替CacheInterceptor,然后注入。这里又会用到一些 套路:bean覆盖套路:beanname规则 等。

方法1:由于ProxyCachingConfiguration没有指定Advice的name,那就用默认的:

  1. <bean id="errorHandler"
  2. class="org.springframework.cache.interceptor.SimpleCacheErrorHandler"/>
  3. <bean name="org.springframework.cache.interceptor.CacheInterceptor#0"
  4. class="org.springframework.cache.interceptor.MyCacheAspectSupport"
  5. p:errorHandler-ref="errorHandler"
  6. p:cacheManager-ref="cacheManager"/>

验证下,可以工作,然而总觉得哪里不对,恩,如果有多个bean。。。

方法2: 注意到ProxyCachingConfiguration中Advisor的name了吗,那就定义Advisor:

  1. <bean id="errorHandler"
  2. class="org.springframework.cache.interceptor.SimpleCacheErrorHandler"/>
  3. <bean name="myCacheAdvice"
  4. class="org.springframework.cache.interceptor.MyCacheAspectSupport"
  5. p:errorHandler-ref="errorHandler"
  6. p:cacheManager-ref="cacheManager"/>
  7. <bean id="annotationCacheOperationSource"
  8. class="org.springframework.cache.annotation.AnnotationCacheOperationSource"/>
  9. <bean name="org.springframework.cache.config.internalCacheAdvisor"
  10. class="org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor" p:adviceBeanName="myCacheAdvice"
  11. p:cacheOperationSource-ref="annotationCacheOperationSource" />

验证下,可以工作。其实还可以有3:

方法3:实现BeanPostProcessor接口

  1. @Autowired private MyCacheAspectSupport mycacheAdvice;
  2. @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  3. if (CacheInterceptor.class.isAssignableFrom(bean.getClass())) {
  4. return mycacheAdvice;
  5. }
  6. return bean;
  7. }

验证下,可以工作。

结尾

好了,问题解决,测了下性能也没太大下降(<1%,场景不同,仅供参考),终于又可以愉快的使用Cacheable等注解了。

spring和相关衍生拥有相当大的代码量,好在有很多套路都是通用的,利用这些套路能让我们解决问题事半功倍。

注: 文中spring版本为4.2.6.RELEASE

本文来自网易云社区,经作者王大喜授权发布。

原文:Spring缓存穿透问题修复

Spring缓存穿透问题修复的更多相关文章

  1. Java高并发缓存架构,缓存雪崩、缓存穿透之谜

    面试题 了解什么是 redis 的雪崩.穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透? 面试官心理分析 其实这是问到缓存必问的,因为缓存雪崩和穿透,是 ...

  2. Redis基础用法、高级特性与性能调优以及缓存穿透等分析

     一.Redis介绍 Redis是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库.缓存服务或消息服务使用.Redis支持多种数据结构,包括字符串.哈希表.链表.集合.有序集合.位图.Hype ...

  3. java集成memcached、redis防止缓存穿透

    下载相关jar,安装Memcached,安装教程:http://www.runoob.com/memcached/memcached-install.html spring配置memcached &l ...

  4. [Redis] - 高并发下Redis缓存穿透解决

    高并发情况下,可能都要访问数据库,因为同时访问的方法,这时需要加入同步锁,当其中一个缓存获取后,其它的就要通过缓存获取数据. 方法一: 在方法上加上同步锁 synchronized //加同步锁,解决 ...

  5. Java模拟并解决缓存穿透

    什么叫做缓存穿透 缓存穿透只会发生在高并发的时候,就是当有10000个并发进行查询数据的时候,我们一般都会先去redis里面查询进行数据,但是如果redis里面没有这个数据的时候,那么这10000个并 ...

  6. SpringBoot微服务电商项目开发实战 --- Redis缓存雪崩、缓存穿透、缓存击穿防范

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  7. Redis缓存穿透和缓存雪崩的面试题解析

    前段时间去摩拜面试,然后,做笔试的时候,遇到了几道Redis面试题目,今天来做个总结.捋一下思路,顺便温习一下之前的知识,如果对您有帮助,左上角点下关注 ! 谢谢 文章目录 缓存穿透 缓存雪崩 大家都 ...

  8. redis的缓存穿透、击穿、雪崩以及实用解决方案

    今天来聊聊redis的缓存穿透.击穿.雪崩以及解决方案,其中解决方案包括类似于布隆过滤器这种网上一搜一大片但是实际生产部署有一定复杂度的,也有基于spring注解通过一行代码就能解决的,其中各有优劣, ...

  9. Spring缓存机制的理解

    在spring缓存机制中,包括了两个方面的缓存操作:1.缓存某个方法返回的结果:2.在某个方法执行前或后清空缓存. 下面写两个类来模拟Spring的缓存机制: package com.sin90lzc ...

随机推荐

  1. web.xml的加载过程是context-param >> listener >> fileter >> servlet

    web.xml的加载过程是context-param >> listener  >> fileter  >> servlet

  2. sublime text 插件及快捷键的使用

    安装插件准备步骤: 1.先安装管理插件,插件必备:package control 1.按ctrl+` 调出console 2.在底部代码行粘贴以下代码并回车: import urllib2,os;pf ...

  3. 一. Selenium介绍

    1. 什么是Selenium 是web自动化测试工具集,主要包括:IDE.Grid.RC(Selenium1.0).WebDriver(Selenium2.0) 与其他工具的不同: 一般的脚本测试工具 ...

  4. if __name__ == "__main__"如何正确理解

    粗略来讲,__name__是当前模块,当模块被直接运行时模块名为__main__.这句话的意思是,当模块被直接执行时,代码将运行,当模块是被导入时,代码不被运行 例如,执行one.py # file ...

  5. 【Cmd命令行】基础—findstr与for循环

    Findstr命令 findstr是Window系统自带的命令,用途是查找指定的一个或多个文件文件中包含(或通过参数 /V来控制不包含)某些特定字符串的行,并将该行完整的信息打印出来,或者打印查询字符 ...

  6. html中defer和window.onload的加载顺序

    结论:defer执行的顺序先于window.onload! 案例: html代码: <!DOCTYPE html> <html lang="en"> < ...

  7. zipfile 解压文件名乱码

    zipfile 中文文件名 解压乱码 上传文件功能模块需求及BUG现象: 环境 mac django 1.11.13 python 3.6 功能需求: 上传一个.zip格式的压缩文件 解压该test. ...

  8. linux下安装使用tar

    安装tar,untar: yum install -y tar yum install -y untar 使用说明: 压缩 zip -r xxx.zip ./* 解压zip文件到当前目录 unzip ...

  9. 利用MFC Picture Control控件 加载bmp,png

    1.在资源视图,选择PictureControl,并且在属性中把Type设置为Bitmap. 2.加载PNG CStatic* pWnd = (CStatic*)GetDlgItem(IDC_PIC) ...

  10. git获取步骤

    $ git init $ git config --global user.name "[name]" $ git config --global user.email [emai ...