为何重造轮子

半年前写了一个注解驱动的缓存,最近提交到了github。缓存大量的被使用在应用中的多个地方,简单的使用方式就是代码先查询缓存中是否存在数据,如果不存在或者缓存过期再查询数据库,并将查询的结果缓存一段时间,缓存key通常是入参的对象或者入参对象的某些属性,有些时候还需要按照某种条件判断是否缓存。可以看到这种功能性代码和具体的业务代码混合在一起的实现方式有很大的代码冗余,即不便于维护也不灵活。使用切面的方式可以很好的抽取功能相似代码冗余的缓存代码,将缓存代码和业务代码隔离开,这样既做到了对业务的无侵入又可以灵活更换具体缓存组件。

其实从spring3之后spring就提供了@Cacheable注解,但是用起来不爽的地方还是太多,例如缓存时间是由cache本身设置的而非在每个@Cacheable注解中指定,这个粒度有点太大了;没有缓存key的前缀设置,不同方法很容易出现key冲突。

怎样重造轮子

鉴于spring3提供的cache注解不太能满足需求,最后决定自己写一个。目标是构造一个简单好用而不是大而全的缓存注解,整个过程陆陆续续花了3天时间,第一天确定技术方案,构建对象和对象间的关系; 第二天写具体的实现和debug; 第三天写demo和test。

确定技术方案的时候看了spring3的cache注解实现和在阿里时使用过的2个cache注解实现。最大是不同点是创建代理类的方式和动态生成cacheKey的实现。

不同的创建代理类的方式:

  • 使用MethodInterceptor+xml配置,最经典的使用方式。缺点是同一个类的方法相互调用时不会被aop拦截,需要使用AopContext.currentProxy()获取代理类。
  • 使用@AspectJ注解,可以有效的减少xml配置,缺点和MethodInterceptor相同。
  • 基于SmartInstantiationAwareBeanPostProcessor+cglib创建代理类。

不同的生成cacheKey的方式:

  • 使用SPEL
  • 使用OGNL
  • 使用正则表达式

最后选择了@AspectJ+SPEL的实现方式

虽然具体的实现方式各自不同,类的调用结构和内部功能都是基本相同的。

  • cacheManager负责cache的管理,包含cache实现的list。
  • cache是具体的缓存实现,可以是redis,ehcache,memcache。
  • keyParser负责动态生成cacheKey。
  • interceptor负责注解的拦截。
  • @Cacheable,@CacheEvict等是具体的缓存注解。

按照上述的功能划分实现相关类后,花了一天的时间来写demo和test,全部的test跑通后就可以使用了。后面增加了一个CacheOperation转换具体的注解,统一对CacheOperation进行处理,代码简化了不少。

实际遇到的问题

实际使用中主要遇到了2个问题,一个是interceptor中catch了所有的Exception并打印错误日志,实际上我们会在应用中定义BizException,当发生预期内的错误时会抛出BizException,而BizException是不需要被拦截打印错误日志的。另一个是问题是并发读写问题,在cache中没有缓存的时候,ThreadA从DB获取数据,ThreadB修改了数据库的数据,ThreadB删除缓存,ThreadA然后put修改之前的数据。原本以为按照业务特点发生并发读写的概率不高,结果发现接口轮询+事务导致频繁发生不一致的情况。缓存失效策略一直是缓存使用中的难题,甚至是计算机科学中两大难题之一。处理数据库并发最常见的2个解决思路是乐观锁和串行化,但是并不适用于解决缓存和数据库的不一致,google了一下也没有找到特别好的解决方案。考虑到应用并没有超高的QPS,短时间的缓存穿透不会造成系统的崩溃,最后通过增加一个redis的缓存删除标识进行解决,这个删除标识会存活5s,在这5s中不会执行put缓存操作从而避免了缓存和数据库的不一致。

使用AOP实现缓存注解的更多相关文章

  1. 使用AOP 实现Redis缓存注解,支持SPEL

    公司项目对Redis使用比较多,因为之前没有做AOP,所以缓存逻辑和业务逻辑交织在一起,维护比较艰难所以最近实现了针对于Redis的@Cacheable,把缓存的对象依照类别分别存放到redis的Ha ...

  2. Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用

    从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...

  3. Spring缓存注解

    从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...

  4. Spring之缓存注解@Cacheable

    https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...

  5. Spring – 缓存注解

    Spring缓存抽象概述 Spring框架自身并没有实现缓存解决方案,但是从3.1开始定义了org.springframework.cache.Cache和org.springframework.ca ...

  6. springboot:自定义缓存注解,实现生存时间需求

    需求背景:在使用springbot cache时,发现@cacheabe不能设置缓存时间,导致生成的缓存始终在redis中. 环境:springboot 2.1.5 + redis 解决办法:利用AO ...

  7. 缓存注解@Cacheable、@CacheEvict、@CachePut使用及注解失效时间

    从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...

  8. Spring缓存注解@Cache使用

    参考资料 http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ http://swiftlet.net/archive ...

  9. springIOC、AOP的一些注解

    springIOC.AOP的一些注解(使用这些注解之前要导入spring框架的一些依赖):    1.注入IOC容器        @Compontent:使用注解的方式添加到ioc容器需要在配置文件 ...

随机推荐

  1. 2015-2016 ACM-ICPC, NEERC, Southern Subregional Contest A Email Aliases(模拟STL vector+map)

    Email AliasesCrawling in process... Crawling failed Time Limit:2000MS     Memory Limit:524288KB     ...

  2. 启动hadoop的命令

    start-all.sh 启动所有的Hadoop守护进程.包括NameNode. Secondary NameNode.DataNode.JobTracker. TaskTrack  stop-all ...

  3. js判断对象还是数组

    1.对于Javascript 1.8.5(ECMAScript 5),变量名字.isArray( )可以实现这个目的 var a=[]; var b={}; Array.isArray(a);//tr ...

  4. [array] leetCode-4-Median of Two Sorted Arrays-Hard

    leetCode-4-Median of Two Sorted Arrays-Hard descrition There are two sorted arrays nums1 and nums2 o ...

  5. 一款非常推荐的用户界面插件----EasyUI

      前  言    easyui是一种基于jQuery的用户界面插件集合. easyui为创建现代化,互动,JavaScript应用程序,提供必要的功能. 使用easyui你不需要写很多代码,你只需要 ...

  6. 表空间与数据文件Offline,online的区别

    首先明确,表空间与数据文件的关系:Oracle数据库表空间有两种,一种smallfile小文件表空间(默认),另一种bigfile大文件表空间: 默认表空间与数据文件的关系:允许一对多的处理方式,一个 ...

  7. css伪类的说明以及使用(css事件)

    CSS伪类的使用(css事件) 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/7670959.html 之前有开发开发App的时候,有同事问我那个列表的条目按下 ...

  8. http下载网页

    //http.c #include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/ ...

  9. 2017年php面试题汇总

    1.http状态码 200 这个没有什么好说的,是代表请求被正常的处理成功了 302 代表临时重定向 400 400表示请求报文中存在语法错误.需要修改后再次发送 403 表明请求访问的资源被拒绝了. ...

  10. .Net 多线程开发优化实践

    互联网产品中微服务.高并发已经成为最基本的要求.所谓高并发就是在同一时刻处理多个服务请求.为了提高高并发场景下的系统稳定性,负载均衡.消息队列等框架和技术应运而生,有效的缓解了高并发对系统整体压力.无 ...