首页 > 代码库 > 【设计优化】-使用缓存(Cache)提高程序性能
【设计优化】-使用缓存(Cache)提高程序性能
缓存(Cache)就是一块用来存放数据的内存空间。主要作用是暂存数据处理结果,并提供下次访问使用。
缓存的使用非常普遍,比如,浏览器都会在本地缓存页面,从而减少HTTP 的访问次数。又如服务器系统开发时,设计人员为一些核心的 API 加上缓存,从而提高系统的缓存时间。
最简单的缓存实现可以使用 HashMap 。当然,这样做会有很多问题,如何时清理无效的数据;如何防止缓存数据过多而导致内存溢出等。一个稍好的方案是使用WeakHashMap,使用弱引用维护一张哈希表,而且可以在内存不足的时候清理数据。
但是作为最专业的实现,就应该有一套专业的缓存框架。比如EHCache、OSCache 和 JBossCache 等。EHCache 缓存出自 Hibernate, 是 Hibernate 框架默认的数据缓存解决方案;OSCache 是由 OpenSympthony 设计的,可以用来缓存任何对象,甚至是缓存部分 JSP 页面或者 HTTP 请求;JBossCache 是由 JBoss 开发、可用于 JBoss 集群间数据共享的缓存框架。
下面,我们以 EHCache 为例,简单介绍一下缓存的基本使用方法。
首先,我们需要在官网 http://ehcache.org 下载 EHCache 包 ,下载完成之后,将 lib 文件夹下的 jar 包添加到工程中就可以使用了。
引入 jar 包之后,在工程的 Classpath 路径下新建一个名为 ehcache.xml 的文件,如上图所示,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true"> <diskStore path="data/ehcache" /> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap" /> </defaultCache> <cache name="sampleCache1" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off"> <persistence strategy="localTempSwap" /> </cache> <cache name="sampleCache2" maxEntriesLocalHeap="1000" eternal="true" memoryStoreEvictionPolicy="FIFO" /> </ehcache>
以上配置文件中首先配置了一个默认的 cache 模板。在程序中使用 EHCache 接口动态生成缓存的时候,会使用这些参数定义新的缓存。随后,定义了两个缓存, 名字分别为 sampleCache1 和 sampleCache2 。
配置文件中主要的参数及含义如下:
必要属性有3个,
maxEntriesLocalHeap:堆内存中最大缓存对象数,值为0则没有限制
maxEntriesLocalDisk:磁盘中的最大对象数,默认为0不限制
eternal:elements是否永久有效,如果为true,timeouts将被忽略,element将永不过期
以下是可选属性
timeToIdleSeconds:失效前的空闲秒数,当eternal为false时,这个属性才有效,0为不限制
timeToLiveSeconds:失效前的存活秒数,创建时间到失效时间的间隔为存活时间,当eternal为false时,这个属性才有效,0为不限制
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
clearOnFlush:当调用flush()是否清除缓存,默认是
memoryStoreEvictionPolicy:内存回收策略,默认回收策略:最近最少使用Least Recently Used,先进先出First In First Out,Less Frequently Used使用频率最低。localTempSwap 则在缓存数目多的时候交换到磁盘上。
timeToIdleSeconds:如果不是永久存储的缓存,那么在 timeToIdleSeconds 指定时间内没有访问一个条目,则移除它
diskPersistent: 磁盘中的条目是否永久保存
diskExpiryThreadIntervalSeconds:清理缓存的线程运行的时间间隔
transactionalMode:设置缓存动作的事务模式
然后我们在 Java 程序中使用预先定义的缓存。
package bupt.xiaoye.charpter2.ehcache; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; public class EhCacheTest { public static void main(String[] args) throws InterruptedException { CacheManager manager = CacheManager.create(); // 取出所有的cacheName String names[] = manager.getCacheNames(); System.out.println("----all cache names----"); for (int i = 0; i < names.length; i++) { System.out.println(names[i]); } System.out.println("----------------------"); // 得到一个cache对象 Cache cache1 = manager.getCache(names[0]); // 向cache1对象里添加缓存 cache1.put(new Element("key1", "values1")); Element element = cache1.get("key1"); // 读取缓存 System.out.println("key1 \t= " + element.getObjectValue()); // 手动创建一个cache(ehcache里必须有defaultCache存在,"test"可以换成任何值) Cache cache2 = new Cache("test", 1, true, false, 2, 3); manager.addCache(cache2); cache2.put(new Element("jimmy", "菩提树下的杨过")); // 故意停1.5秒,以验证是否过期 Thread.sleep(1500); Element eleJimmy = cache2.get("jimmy"); //1.5s < 2s 不会过期 if (eleJimmy != null) { System.out.println("jimmy \t= " + eleJimmy.getObjectValue()); } //再等上0.5s, 总时长:1.5 + 0.5 >= min(2,3),过期 Thread.sleep(500); eleJimmy = cache2.get("jimmy"); if (eleJimmy == null) { System.out.println("jimmy \t= null" ); } // 取出一个不存在的缓存项 System.out.println("fake \t= " + cache2.get("fake")); manager.shutdown(); } }
针对 EHCache,我们还可以写一个简单的工具类,专门针对 EHCache 做各种操作。
package bupt.xiaoye.charpter2.ehcache; import java.io.Serializable; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; public class EHCacheUtil { private static CacheManager manager; static { try { manager = CacheManager.create(); } catch (CacheException e) { e.printStackTrace(); } } public static void put(String cachename, Serializable key, Serializable value) { manager.getCache(cachename).put(new Element(key, value)); } public static Object get(String cachename, Serializable key) { try { Element e = manager.getCache(cachename).get(key); if (e == null) return null; return e.getObjectValue(); } catch (IllegalStateException e) { e.printStackTrace(); } return null; } }
有了以上工具类,便可以很方便的在实际工作中使用EHCache。
在方法加入缓存的时候,可以使用最原始的编码方式,根据传入的参数构造key,然后去缓存中查找结果。这种实现方式的好处是代码比较直白,简单,缺点是缓存中间和业务层代码紧密耦合,依赖性强。
下面我们介绍基于动态代理的缓存解决方案。基于动态代理的缓存方案的最大好处是,在业务层,无需关注对缓存的操作,缓存操作代码被完全独立并隔离,并且对一个新的函数方法加入缓存不会影响原有的方法实现,是一种非常灵活的软件结构。
最大的好处便是:无需修改一个逻辑方法的代码,便可以为它加上缓存功能,提高其效率。
假如有下面一个方法,它用于对一个整数做因式分解。为这个类创建动态代理,并测试两者的性能:
package bupt.xiaoye.charpter2.ehcache; import java.io.Serializable; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; class HeavyMethodDemo{ public String heavyMethod(int num) throws Exception{ StringBuffer sb = new StringBuffer(); // do something whith num Thread.sleep(20); return sb.toString(); } } public class CglibHeavyMethodInterceptor implements MethodInterceptor { HeavyMethodDemo real = new HeavyMethodDemo(); @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { String v = (String) EHCacheUtil.get("sampleCache1", (Serializable) arg2[0]); if (v == null) { v = real.heavyMethod((Integer) arg2[0]); EHCacheUtil.put("sampleCache1", (Integer) arg2[0], v); } return null; } /** * 带有缓存功能的代理类 * @return */ public static HeavyMethodDemo newCachedHeavyMethod() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HeavyMethodDemo.class); enhancer.setCallback(new CglibHeavyMethodInterceptor()); HeavyMethodDemo cglibProxy = (HeavyMethodDemo) enhancer.create(); return cglibProxy; } /** * 不带缓存功能的主题 * @return */ public static HeavyMethodDemo newHeavyMethod(){ return new HeavyMethodDemo(); } public static void main(String[] args)throws Exception{ HeavyMethodDemo m =newCachedHeavyMethod(); long begin = System.currentTimeMillis(); for(int i=0;i<100;i++){ m.heavyMethod(21474586); } System.out.println(System.currentTimeMillis()-begin); m = newHeavyMethod(); begin = System.currentTimeMillis(); for(int i=0;i<100;i++){ m.heavyMethod(21474586); } System.out.println(System.currentTimeMillis()-begin); } }
经过测试,使用缓存时耗时 1144 ms。不使用缓存时耗时2152ms。
【设计优化】-使用缓存(Cache)提高程序性能