本地缓存GuavaCache的使用

发表时间:2018-02-27 22:13:06 浏览量( 59 ) 留言数( 0 )

学习目标:

1、了解缓存的作用

2、了解Guava对缓存的操作方法


学习过程:

一、为什么需要缓存

    缓存需要消耗一些内存空间达到提升速度的功能。如果一些数据需要多次的访问,缓存起来效率会高很多,但是也要注意所以如果一条数据不需要多次的访问,也就没有缓存起来,因为这样会消耗内容,还有就是缓存存放的数据总量不会超出内存容量,如果大量占用了内存也会导致系统变慢的。

   还需要考虑的就是缓存放到哪里?一般可以分为本地缓存和分布式缓存两种方式。本地缓存就只能在本地访问,在需要缓存量不是很大的时候,同时对缓存的数据也不是很重要的情况下,本地缓存操作方便,但是如果缓存量很大,为了提供内存的利用率,提高缓存的稳定性,可以采用分布式缓存的方案。

   本地缓存,如果是web服务呢,我们经常使用的session,session当然也是一种非常好用的缓存,但是只针对某个用户的,今天我们将会学习google的Guava的包封装的一个缓存工具类。

二、Guava的缓存封装工具

平时我们查询数据库时,并没有把数据缓存起来,如果需要缓存,我们一般的代码思路

(1)、先判断缓存是否有此数据

(2)、如果有直接从缓存取值

(3)、如果没有就查询数据,并把数据放到缓存中

这样模板式的处理比较死板,Guava帮我们封装一下,就不需要写得这么复杂了。还需要考虑的就是缓存时效、最大限制、缓存的清空等。Guava比较都封装得比较好了。

1、基本使用

导入包

<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>14.0.1</version>
		</dependency>

使用代码:

@Service
public class GoodService {

	@Autowired
	private GoodsDao goodsDao;
	
	private final static String PREFIX="good:";

	LoadingCache<String, Goods> goodsCache = CacheBuilder.newBuilder().maximumSize(1000)
			.expireAfterAccess(20, TimeUnit.SECONDS).build(new CacheLoader<String, Goods>() {

				@Override
				public Goods load(String goodId) throws Exception {
					Goods good = goodsDao.queryByid(Integer.valueOf(goodId.split(":")[1]));
					if (good == null) {
						good = new Goods();	
					}
					return good;
				}
			});

	public Goods getGoodCache(int goodId) {
		Goods goods=null;
		try {
			goods= goodsCache.get(PREFIX+goodId);
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return goods;
	}
	
	/**
	 * 手动清除
	 * @param goodId
	 * @return
	 */
	public void clearGoodCache(int goodId) {
		goodsCache.invalidate(PREFIX+goodId);
	}
	
	public void clearAllGoodCache() {
		goodsCache.invalidateAll();
	}

}

测试代码,你可以再中间清空缓存得操作注释和不注释得时候得区别。

@RunWith(SpringJUnit4ClassRunner.class) // 使用junit4进行测试
@ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加载配置文件
public class GoodServiceTest {
	
	@Autowired
	private GoodService goodService;
	
	@Test
	public void testGetUserCache() {
		
		Goods good=goodService.getGoodCache(9);
		
		//goodService.clearGoodCache(good.getGoodsId());
		
		Goods good1=goodService.getGoodCache(9);
		
		System.out.println(good.getGoodsName());
		
		System.out.println(good1.getGoodsName());
		
	}

}

三、缓存回收

  什么时候某个缓存项就不值得保留了?Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。

1、基于容量的回收(size-based eviction)

    如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。

   另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。比如我可以设置价钱越高权重越高。

 

2、定时回收(Timed Eviction)

    CacheBuilder提供两种定时回收的方法:

    expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。

    expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

定时回收周期性地在写操作中执行,偶尔在读操作中执行。代码修改如下:

	LoadingCache<String, Goods> goodsCache = CacheBuilder.newBuilder().maximumSize(1000)
			.expireAfterAccess(20, TimeUnit.SECONDS).weigher(new Weigher<String, Goods>() {

				public int weigh(String key, Goods value) {
					
					return ((Double)value.getGoodsCash()).intValue();
				}
			}).build(new CacheLoader<String, Goods>() {

				@Override
				public Goods load(String goodId) throws Exception {
					Goods good = goodsDao.queryByid(Integer.valueOf(goodId.split(":")[1]));
					if (good == null) {
						good = new Goods();
					}
					return good;
				}
			});

	public Goods getGoodCache(int goodId) {
		Goods goods = null;
		try {
			goods = goodsCache.get(PREFIX + goodId);
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return goods;
	}


3、基于引用的回收(Reference-based Eviction)

    通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

    CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。

    CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。

    CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。

4、显式清除

    任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

    个别清除:Cache.invalidate(key) 

    批量清除:Cache.invalidateAll(keys) 

    清除所有缓存项:Cache.invalidateAll()


四、清除什么时候发生

    也许这个问题有点奇怪,如果设置的存活时间为一分钟,难道不是一分钟后这个key就会立即清除掉吗?我们来分析一下如果要实现这个功能,那Cache中就必须存在线程来进行周期性地检查、清除等工作,很多cache如redis、ehcache都是这样实现的。

    但在GuavaCache中,并不存在任何线程!它实现机制是在写操作时顺带做少量的维护工作(如清除),偶尔在读操作时做(如果写操作实在太少的话),也就是说在使用的是调用线程,参考如下示例:

    这在GuavaCache被称为“延迟删除”,即删除总是发生得比较“晚”,这也是GuavaCache不同于其他Cache的地方!这种实现方式的问题:缓存会可能会存活比较长的时间,一直占用着内存。如果使用了复杂的清除策略如基于容量的清除,还可能会占用着线程而导致响应时间变长。但优点也是显而易见的,没有启动线程,不管是实现,还是使用起来都让人觉得简单(轻量)。

    如果你还是希望尽可能的降低延迟,可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp(),ScheduledExecutorService可以帮助你很好地实现这样的定时调度。不过这种方式依然没办法百分百的确定一定是自己的维护线程“命中”了维护的工作。