首页 > 代码库 > 【设计优化】-使用缓存(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)提高程序性能