为何重造轮子

半年前写了一个注解驱动的缓存,最近提交到了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. caffe源码分析 vector<Blob<Dtype>*>& bottom

    Blob:4个维度 n x c x h x w: bottom[0] .bottom[1]代表该层有几个输入. bottom[0]->count(): 输入中,元素的总维数(个数) bottom ...

  2. Android 开发笔记___drawable

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  3. css响应式布局

    以设计稿为准,假设设计稿竖屏宽度为750px,取根元素的font-size为50px 在iphone6(375px)下,deviceWidth=7.5rem, 这个就是viewPort中的device ...

  4. Flexbox 布局教程

    Flex 布局是什么? Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性. 任何一个容器都可以指定为 Flex 布局. 使用 fle ...

  5. selenium-iframe操作

    iframe,又叫浮动帧标记,是内嵌的网页元素,可以将一个html文件嵌入到另一个html文件中显示. 最典型的案例就是163邮箱登录:http://mail.163.com/,被绿色覆盖的部分即为i ...

  6. ssm框架下web项目,web.xml配置文件的作用

    1. web.xml中配置了CharacterEncodingFilter,配置这个是拦截所有的资源并设置好编号格式. encoding设置成utf-8就相当于request.setCharacter ...

  7. table固定头部,表格tbody可上下左右滑动

    当表格头部固定时,需要分为两个表格来做:一部分是thead,一部分是tbody,具体实现方式如下: html代码: <div class="table_box_big"> ...

  8. app.config 配置多项 配置集合 自定义配置(2)

    上一篇说了利用app.config自定义节点配置,那是利用工具来实现,其实也一全部编码的方式来实现.举一个栗子.Simpson一家有父亲James,母亲Kate,和三个儿女Jim,Aaron和Luka ...

  9. 我的第一个python web开发框架(16)——产品分类管理

    产品分类管理的html页面之前忘记做了,这次附件里补上. 好了先上图 从页面效果图来看,我们需要开发列表获取接口.添加接口.单条记录获取接口.编辑接口和删除接口 对于产品分类列表,我们将使用jqgri ...

  10. ZooKeeper全面介绍

    ZooKeeper简介 ZooKeeper是分布式服务框架,主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务.状态同步服务.集群管理.分布式应用配置项的管理等等.   ZooKe ...