`
j2eetop
  • 浏览: 59629 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

《开源框架那点事儿16》:缓存相关代码的演变

 
阅读更多

问题引入

上次我参与某个大型项目的优化工作,由于系统要求有比较高的TPS,因此就免不了要使用缓冲。

该项目中用的缓冲比较多,有MemCache,有Redis,有的还需要提供二级缓冲,也就是说应用服务器这层也可以设置一些缓冲。

当然去看相关实现代代码的时候,大致是下面的样子。

  1. public void saveSomeObject(SomeObject someObject){  
  2.   
  3.     MemCacheUtil.put("SomeObject",someObject.getId(),someObject);  
  4.   
  5.     //下面是真实保存对象的代码  
  6.   
  7.    
  8.   
  9. }  
  10.   
  11. public SomeObject getSomeObject(String id){  
  12.   
  13.     SomeObject someObject = MemCacheUtil.get("SomeObject",id);  
  14.   
  15.     if(someObject!=null){  
  16.   
  17.          someObject=//真实的获取对象  
  18.   
  19.          MemCacheUtil.put("SomeObject",someObject.getId(),someObject);  
  20.   
  21.     }  
  22.   
  23.     return someObject;  
  24.   
  25. }  
public void saveSomeObject(SomeObject someObject){

    MemCacheUtil.put("SomeObject",someObject.getId(),someObject);

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject = MemCacheUtil.get("SomeObject",id);

    if(someObject!=null){

         someObject=//真实的获取对象

         MemCacheUtil.put("SomeObject",someObject.getId(),someObject);

    }

    return someObject;

}


 

很明显与缓冲相关的代码全部是耦合到原来的业务代码当中去的。

后来由于MemCache表现不够稳定,而且MemCache的功能,也可以由Redis完全进行实现,于是就决定从系统中取消MemCache,换成Redis的实现方案,于是就改成如下的样子:

  1. public void saveSomeObject(SomeObject someObject){  
  2.   
  3.     RedisUtil.put("SomeObject",someObject.getId(),someObject);  
  4.   
  5.     //下面是真实保存对象的代码  
  6.   
  7.    
  8.   
  9. }  
  10.   
  11. public SomeObject getSomeObject(String id){  
  12.   
  13.     SomeObject someObject = RedisUtil.get("SomeObject",id);  
  14.   
  15.     if(someObject!=null){  
  16.   
  17.          someObject=//真实的获取对象 <span></span>RedisUtil.put("SomeObject",someObject.getId(),someObject);  
  18.   
  19.     }  
  20.   
  21.     return someObject;  
  22.   
  23. }  
public void saveSomeObject(SomeObject someObject){

    RedisUtil.put("SomeObject",someObject.getId(),someObject);

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject = RedisUtil.get("SomeObject",id);

    if(someObject!=null){

         someObject=//真实的获取对象 <span></span>RedisUtil.put("SomeObject",someObject.getId(),someObject);

    }

    return someObject;

}


 

这一通改下来,开发人员已经晕头晕脑的了,后来感觉性能还是不够高,这个时候,要把一些数据增加二级缓冲,也就是说,本地缓冲有就取本地,本地没有就取远程缓冲  

于是,上面的代码又是一通改,变成下面这个样子:

  1. public void saveSomeObject(SomeObject someObject){  
  2.   
  3.     LocalCacheUtil.put("SomeObject",someObject.getId(),someObject);  
  4.   
  5.     RedisUtil.put("SomeObject",someObject.getId(),someObject);  
  6.   
  7.     //下面是真实保存对象的代码  
  8.   
  9.    
  10.   
  11. }  
  12.   
  13. public SomeObject getSomeObject(String id){  
  14.   
  15.     SomeObject someObject = LocalCacheUtil.get("SomeObject",id);  
  16.   
  17.     if(someObject!=null){  
  18.   
  19.         return someObject;  
  20.   
  21.     }  
  22.   
  23.     someObject = RedisUtil.get("SomeObject",id);  
  24.   
  25.     if(someObject!=null){  
  26.   
  27.          someObject=//真实的获取对象   
  28.   
  29.          RedisUtil.put("SomeObject",someObject.getId(),someObject);  
  30.   
  31.     }  
  32.   
  33.     return someObject;  
  34.   
  35. }  
public void saveSomeObject(SomeObject someObject){

    LocalCacheUtil.put("SomeObject",someObject.getId(),someObject);

    RedisUtil.put("SomeObject",someObject.getId(),someObject);

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject = LocalCacheUtil.get("SomeObject",id);

    if(someObject!=null){

        return someObject;

    }

    someObject = RedisUtil.get("SomeObject",id);

    if(someObject!=null){

         someObject=//真实的获取对象 

         RedisUtil.put("SomeObject",someObject.getId(),someObject);

    }

    return someObject;

}


 

但是这个时候就出现一个问题: 

由于在某一时刻修改值的只能是某一台计算机,这个时候,其它的计算机的本地缓冲实际上与远程及数据库中的数据会不一致,这个时候,可以有两种办法实现,一种是利用Redis的请阅发布机制进行数据同步,这种方式,会保证数据能够被及时同步。

另外一种方法就是设置本地缓冲的有效时间比较短,这样,允许在比较短的时间段内出现数据不一致的情况。

不管怎么样,功能是实现了,程序员小伙伴这个时候已经改得眼睛发黑,手指发麻,几乎接近崩溃了。

很明显这种实现方式是不好的,于是项目组又提出了改进意见,能否采用注解方式进行标注,让程序员只要声明就可以?Good idea,于是,又变成了下面的样子:

  1. @Cache(type="SomeObject",parameter="someObject",key="${someObject.id}")  
  2.   
  3. public void saveSomeObject(SomeObject someObject){  
  4.   
  5.     //下面是真实保存对象的代码  
  6.   
  7.    
  8.   
  9. }  
  10.   
  11. @Cache("SomeObject",key="${id}")  
  12.   
  13. public SomeObject getSomeObject(String id){  
  14.   
  15.     SomeObject someObject=//真实的获取  
  16.   
  17.     return someObject;  
  18.   
  19. }  
@Cache(type="SomeObject",parameter="someObject",key="${someObject.id}")

public void saveSomeObject(SomeObject someObject){

    //下面是真实保存对象的代码

 

}

@Cache("SomeObject",key="${id}")

public SomeObject getSomeObject(String id){

    SomeObject someObject=//真实的获取

    return someObject;

}


 

这个时候,程序员们的代码已经非常清爽了,里面不再有与缓冲相关的部分内容,但是引入一个新的问题,就是处理注解的代码怎么写?需要引入容器,比如:Spring,这些Bean必须被容器所托管,如果直接new一个实例,就没有办法用缓冲了。还有一个问题是:程序员的工作量虽然有所节省,但是还是要对程序代码有侵入性,需要引入这些注解,如果要增加超越现有注解的功能,还是需要重新写过这些类,引入其它的注解,修改现有的注解。

所以,上面是个可以接受的方案,但明显还不是很好的方案。

假如有一个程序员火大了,他发出下面的抱怨:“我只管做我的业务,放不放缓冲和我有1毛钱关系么?总因为缓冲的事情让我改来改去,程序改得乱七八糟不说,我的时间,我的工作进度都影响了谁来管?以后和缓冲相关的事情别他妈的来烦我!”,作为架构师的你,你怎么看?最起码,我觉得他是说得非常有道理的。我们再返过头来看看最原始的代码:

  1. public void saveSomeObject(SomeObject someObject){  
  2.   
  3.     //下面是真实保存对象的代码  
  4.   
  5.    
  6.   
  7. }  
  8.   
  9. public SomeObject getSomeObject(String id){  
  10.   
  11.     SomeObject someObject=//真实的获取  
  12.   
  13.     return someObject;  
  14.   
  15. }  
public void saveSomeObject(SomeObject someObject){

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject=//真实的获取

    return someObject;

}


 

这里是干干净净的业务代码,和缓冲没有一点关系。后来由于性能方面的要求,需要做缓冲,OK,这一点是事实,但是用什么缓冲或怎么缓冲,与程序员确实是没有什么关系的,因此,是不是可以不让程序员参与,就可以优雅的做到添加缓冲功能呢?答案当然是肯定的。 

需求整理

  1. 代码当中,不要体现与缓冲相关的内容,也就是说做不做缓冲及怎么做缓冲不要影响到业务代码
  2. 不管是从容器中取实例还是new实例,都可以同样的起作用,也就是说可以不必依赖具体的容器

解决思路:

放不放缓冲、怎么放缓冲、缓冲有效时间等等,这些内容是在运行期发现存在性能瓶颈,然后提交给程序员来进行优化的。为此,我们设计了一个配置来描述这些缓冲相关的声明。

当然,这个配置文件的结构,可以根据自己所采用的缓冲框架来进行相应的定义。

比如:

  1. <redis-caches>  
  2.   
  3.   <redis-cache type="org.tinygroup.redis.test.UserDao">  
  4.   
  5.      <redis-method method-name="saveUser">  
  6.   
  7.            <redis-expire value="1000"></redis-expire>  
  8.   
  9.            <redis-string type="user" key="${user.id}" paramter-name="user"><redis-string>  
  10.   
  11.      </redis-method>  
  12.   
  13.   </redis-cache>  
  14.   
  15.   <redis-cache type="org.tinygroup.redis.test.UserDao">  
  16.   
  17.      <redis-method method-name="getUser">  
  18.   
  19.            <redis-expire value="1000"></redis-expire>  
  20.   
  21.            <redis-string type="user" key="${id}" ><redis-string>  
  22.   
  23.      </redis-method>  
  24.   
  25.   </redis-cache>  
  26.   
  27. </redis-caches>  
<redis-caches>

  <redis-cache type="org.tinygroup.redis.test.UserDao">

     <redis-method method-name="saveUser">

           <redis-expire value="1000"></redis-expire>

           <redis-string type="user" key="${user.id}" paramter-name="user"><redis-string>

     </redis-method>

  </redis-cache>

  <redis-cache type="org.tinygroup.redis.test.UserDao">

     <redis-method method-name="getUser">

           <redis-expire value="1000"></redis-expire>

           <redis-string type="user" key="${id}" ><redis-string>

     </redis-method>

  </redis-cache>

</redis-caches>


 

我们在实际应用当中,配置比上面的示例更完善,那现在我先讲一下上面的两段配置的含义。

在UserDao的saveUser的时候,会同步的把User数据放到Redis中进行缓冲,缓冲时间为1秒,存放的缓冲数据的类型为user,键值为${user.id},也就是要保存的用户的主健。实际进入到Redis的时候,在Redis中的健值是由上面类型与key的共同组成的。

在调用UserDao的getUser的时候,会先从缓冲中获取类型为user,键值为${id}的数据,如果缓冲中在,则取出并返回,如果缓冲中没有,则从原有业务代码中取出值并放入缓冲,然后返回此对象。

哇,这个时候,就非常爽了,只要通过声明就可以做到对缓冲的处理了,但是一个问题就出来了,如何实现上面的需求呢?

通过配置文件外置,确实做到了对业务代码的0侵入,但是如何为原有业务增加缓冲相当的业务逻辑呢?由于需求2要求可以new,也可以从容器中获取对象实例,因此利用容器AOP解决的跑就被封死了,因此,就得引入字节码的方式来进行解决。

具体实现

写一个基于Maven的缓冲代码处理插件,在编译后增加此处理插件,根据配置文件对原有代码进行扫描并修改其字节码以添加缓冲相关处理逻辑。

现在只要使用Maven进行compile或install就可以自动添加缓冲相关的逻辑到class文件中了。

至此,我们已经分析了缓冲代码直接耦合到代码中,并分析了其中的缺点,最终演化了注解方式,外置配置方式,并简要介绍了实现方法。

具体实现,采用的技术就比较多了,有Maven插件、有Asm、有模板引擎还有Tiny框架的一些基础工程,如:VFS,FileResolver等等。

如果采用Tiny框架,可以直接拿来用,如果不用Tiny框架,可以参照上面的思路做自己的实现。

 


 


 

 

欢迎访问开源技术社区:http://bbs.tinygroup.org。本例涉及的代码和框架资料,将会在社区分享。《自己动手写框架》成员群:228977971,让我们一起动手,了解开源框架的奥秘!

11
5
分享到:
评论
7 楼 fxf-风翔飞 2015-08-17  
j2eetop 写道
fxf-风翔飞 写道
这样的话配置项会不会太多了,不方便管理
最好感觉是用注解,或者对该包做拦截,通过对应的命名规则去做缓存的存取

注解的话会耦合,以后切换缓冲方案不方便。


注解也一样,通过对注解做处理的过程也是可以进行切换的
切换缓冲方案直接换个处理流程就行
6 楼 fxf-风翔飞 2015-08-17  
j2eetop 写道
fxf-风翔飞 写道
这样的话配置项会不会太多了,不方便管理
最好感觉是用注解,或者对该包做拦截,通过对应的命名规则去做缓存的存取

注解的话会耦合,以后切换缓冲方案不方便。


那就做拦截呗,以后切换方案换拦截器就行
5 楼 j2eetop 2015-06-25  
zhangchengtest 写道
我也感觉注解要好点吧

注解的话会耦合,以后切换缓冲方案不方便。
4 楼 j2eetop 2015-06-25  
fxf-风翔飞 写道
这样的话配置项会不会太多了,不方便管理
最好感觉是用注解,或者对该包做拦截,通过对应的命名规则去做缓存的存取

注解的话会耦合,以后切换缓冲方案不方便。
3 楼 zhangchengtest 2015-06-24  
不过使用注解new的话就不能用缓存了 怎么样写缓存代码处理插件
2 楼 zhangchengtest 2015-06-24  
我也感觉注解要好点吧
1 楼 fxf-风翔飞 2015-06-19  
这样的话配置项会不会太多了,不方便管理
最好感觉是用注解,或者对该包做拦截,通过对应的命名规则去做缓存的存取

相关推荐

    Java缓存框架Java缓存框架

    EhCache EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。 主要的特性有: 1. 快速. 2. 简单. 3. 多种缓存策略 4. 缓存数据有两级:内存和磁盘,因此无需担心容量...

    SSM(详细注释代码清晰)开源框架

    04、框架集成了代码表缓存的功能,查询时不需要再关联到字典表;通过拦截器进行权限检查、日志输出等操作; 05、框架实现了文件上传共通、电子文档导出、校验、全局异常处理、分页等共通,具体参见相关画面的相关...

    win7缓存清理代码

    win7缓存清理代码,使win7能够快速自动清理干净,包含剪贴板

    c#Aries开源框架系统源码

    框架自带分布式缓存(MemCache、Redis),轻松实现分布式缓存。 1:Aries.DataBase 初始:数据表脚本、数据脚本、数据库设计文档。 2:Aries.DevFramework 框架源代码。 3:Aries.Document API文档或帮助类文档...

    thinkphp php开源框架

    借鉴了国外很多优秀的框架和模式,使用面向对象的开发结构和MVC模式,融合了Struts的Action思想和JSP的TagLib(标签库)、RoR的ORM映射和ActiveRecord模式,封装了CURD和一些常用操作,单一入口模式等,在模版引擎、...

    大型网站架构演变和知识体系

    架构演变第二步:增加页面缓存 架构演变第三步:增加页面片段缓存 架构演变第四步:数据缓存 架构演变第五步: 增加webserver 架构演变第六步:分库,数据库集群 架构演变第七步:分表、DAL和分布式缓存 架构演变第...

    java 缓存系统实战(安装和实例代码)

    介绍了memcached缓存开源框架在windows下的安装,以及如何用java访问缓存server,并结合原理和代码讲解。代码测过可用。

    安卓图片加载缓存相关-引用开源框架通过AsyncHttpClient实现网络图片查看器.rar

    引用开源框架通过AsyncHttpClient实现网络图片查看器.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。

    缓存、缓存算法和缓存框架简介 - 文章 - 伯乐在线.pdf

    缓存、缓存算法和缓存框架简介 - 文章 - 伯乐在线.pdf 来自http://blog.jobbole.com/30940/

    android中使用afinal开源框架一行代码显示网络图片

    android快速开发框架afinal的特点: 使用简单,无需配置但可配置 在listview和gridview等容器加载图片的时候快速滑动无错误现象 支持线程池并发数量配置 支持内存缓存大小配置 支持磁盘大小缓存配置,和缓存...

    Java缓存框架 Ehcache.zip

    EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。 下图是 Ehcache 在应用程序中的位置: 主要的特性有: 1. 快速.2. 简单.3. 多种缓存策略4. 缓存数据有两级:...

    金民一期婚姻管理测试系统_开发框架功能点-分布式缓存.pdf

    金民一期婚姻管理测试系统_开发框架功能点-分布式缓存.pdf金民一期婚姻管理测试系统_开发框架功能点-分布式缓存.pdf金民一期婚姻管理测试系统_开发框架功能点-分布式缓存.pdf金民一期婚姻管理测试系统_开发框架功能...

    各种图片缓存技术代码

    图片缓存技术,包括16个demo,根据手势缩放图片、afinal框架实现图片的简单异步缓存加载、andengine中直接加载多张小图片合成一张大图片生成动画精灵、Android 图片缓存、加载器、android端用于异步加载图片,内存...

    缓存、缓存算法和缓存框架简介.docx

    缓存、缓存算法和缓存框架简介

    Guava-Cache本地缓存案例代码

    Guava-Cache本地缓存案例代码,包含:缓存创建、缓存删除、缓存获取、缓存失效监听器、缓存加载、缓存淘汰策略。

    redis缓存的示例代码

    使用redis缓存的代码示例,框架使用的springboot,有兴趣的可以参考

    Android代码-afinal框架实现图片的简单异步缓存加载.rar

    afinal框架实现图片的简单异步缓存加载.rar

    rock-0.9开源框架

    rock框架是本人开发的一个开源框架。 rock框架的核心主要是rock-dbutils,rock-dbutils是一个轻量级的ORM框架,对jdbc进行了浅层次的封装。是本人在教学和项目开发中慢慢积累开发而成,用来代替烦人的复杂的hibernate...

    android异步缓存框架

    android异步缓存框架

    图片缓存框架使用

    利用现有的开源的图片缓存框架实现的图片缓存下载,测试程序在TestLoadImage中

Global site tag (gtag.js) - Google Analytics