为何重造轮子

半年前写了一个注解驱动的缓存,最近提交到了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. 第一数学归纳法 vs 第二数学归纳法 vs 良序定理

    相关: 第一数学归纳法 vs 第二数学归纳法 vs 良序定 第二数学归纳法:硬币问题和堆垛游戏 第一数学归纳法:施塔特中心的地板砖 良序原理:算术基本定理的证明 From : Mathematics ...

  2. P3003 [USACO10DEC]苹果交货Apple Delivery

    题目描述 Bessie has two crisp red apples to deliver to two of her friends in the herd. Of course, she tr ...

  3. python的学习之路day1

    软件:python3.pycharm开发工具 python的开始:print("hello world") 注意:python3需要加上() 1.变量是什么:在程序运行过程中它的值 ...

  4. Magicodes.Admin.Core开源框架总体介绍

    框架说明 Magicodes.Admin.Core框架在ABP以及ASP.NET ZERO的基础上进行了封装和完善,目前基于.NET Core 2.0+(Framework版本),由于部分组件在.NE ...

  5. 【Tesseract】Tesseract API在VS 2013中的配置以及调用

    想要在VS中使用Tesseract库,必须使用经过相对应的VS版本编译过的dll以及lib.比如在VS 2013中,就必须使用在VS 2013中编译过的Tesseract库. 这里我给出经过VS 20 ...

  6. MUI框架开发HTML5手机APP(一)--搭建第一个手机APP

      前  言 JRedu 随着HTML5的不断发展,移动开发成为主流趋势!越来越多的公司开始选择使用HTML5开发手机APP,而随着手机硬件设备配置的不断提升,各种开发框架的不断优化,也使着H5开发的 ...

  7. C# 读写文本文件乱码解决方案

    在使用C#对文本文件读取的时候,如果其中包含了中文,经常会出现乱码.一般解决是在StreamReader加一个编码,我使用的是Encoding.UTF8,一般情况下使用这个参数就可以.但是,在这次我使 ...

  8. [转载] Thrift-server与spring集成

    转载自http://shift-alt-ctrl.iteye.com/blog/1990026 Thrift服务server端,其实就是一个ServerSocket线程 + 处理器,当Thrift-c ...

  9. label的作用是什么?是怎么用的?

    label标签是用来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上. 主要有以下两种用法用法: 一. <label for = "Name&qu ...

  10. Django 入门案例开发(下)——创建项目应用及模型类

    前面两章是在已经开发好的项目上用来描述环境和业务,这一章创建一个全新的项目来用作开发,你可以跟着我的步骤进行开发,如果有不理解的地方可以给我留言. 今天的任务是创建好项目和用户(users)应用及让它 ...