《深入分布式缓存 》第4章Ehcache 与guava cache

一 序

   本文属于《深入分布式缓存 》读书笔记,第一章:缓存为王主要介绍缓存概念,以及引入缓存的背景:提升用户体验。还介绍了缓存的分类,第二章主要介绍分布式理论。个人觉得第二章可以去掉,毕竟是泛泛的介绍。还是专门去看有主题的书比较好,比如《<从PAXOS到ZOOKEEPER分布式一致性原理与实践》。第4章主要介绍EHcache。因为实际项目采用了guava +redis. 所以本文打算重点看看guava cache。

二 Ehcache 

  先说下官网:http://www.ehcache.org/ 貌似需要翻墙。

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

    Spring 提供了对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache),但本身不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。

 Ehcache的特点:

  • 快速
  • 简单
  • 多种缓存策略
  • 缓存数据有两级:内存和磁盘,因此无需担心容量问题
  • 缓存数据会在虚拟机重启的过程中写入磁盘
  • 可以通过RMI、可插入API等方式进行分布式缓存
  • 具有缓存和缓存管理器的侦听接口
  • 支持多缓存管理器实例,以及一个实例的多个缓存区域
  • 提供Hibernate的缓存实现

适用场景:

1 数据更新比较少的情况,

2 对并发、一致性要求不高的情况。本地缓存的特性,不适合解决不同服务器间缓存同步的问题,更推荐redis等集中式缓存。

优化方案:1 定时轮询。2.主动通知。

三 guava cache

先贴一下官网:https://github.com/google/guava/wiki/CachesExplained

  3.1 jvm缓存

  就是堆缓存。其实就是创建一些全局变量,如 Map、List 之类的容器用于存放数据。
优点:简单,占用内存小。
缺点:不具备缓存的常见操作,如:
只能显式的写入,清除数据。
不能按照一定的规则淘汰数据,如 LRU,LFU,FIFO 等。
清除数据时的回调通知。
其他一些定制功能等。

   3.2  guava cache试用场景

Guava 的 Cache跟ehcache 一样也是对内缓存,适用单节点使用。

  • 你愿意消耗一些内存空间来提升速度。
  • 你预料到某些键会被查询一次以上。
  • 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)

3.3 guava cache的创建方式

  • CacheLoader
  • Callable callback

     使用缓存前,首先问自己一个问题:有没有合理的默认方法来加载或计算与键关联的值?如果有的话,你应当使用CacheLoader。如果没有,或者你想要覆盖默认的加载运算,同时保留"获取缓存-如果没有-则计算"[get-if-absent-compute]的原子语义,你应该在调用get时传入一个Callable实例。缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选的,因为它可以更容易地推断所有缓存内容的一致性。

   本篇后面例子将以cacheloader方式。

3.4 缓存数据的删除

    Guava provides three basic types of eviction: size-based eviction, time-based eviction, and reference-based eviction

基于容量回收、定时回收和基于引用回收。

基于容量回收(size-based eviction:用CacheBuilder.maximumSize(long)

定时回收(Timed Eviction):CacheBuilder提供两种定时回收的方法:

  • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
  • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

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

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

主动删除:

书上的并发场景使用:多个key并发请求的case,为了缓解后端压力,对同一个key只允许一个请求去回源获取数据,其他请求阻塞等待结果。

四 demo

   需求:维护一个已开通服务的城市列表。从业务场景来说,读数据是远大于变更的,通常新开通城市会以天为单位。

所以缓存到本地就行,使用guava cache适合。

city pojo

package com.daojia.guava.cache;

import java.io.Serializable;

public class City implements Serializable {

	private static final long serialVersionUID = 1L;

	private int id;
	
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}	
	
}

 cache util

package com.daojia.guava.cache;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;



public class CacheUtil {

	  private static final String CACHE_KEY = "LocalCache";
	  
	  private LoadingCache<String, Map<Integer, City>> cache =
              CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.DAYS).build(new CacheLoader<String, Map<Integer, City>>() {
                  @Override
                  public Map<Integer, City> load(String key) throws Exception {
                	  System.out.println("load city from service:");
                      List<City> allCity = new ArrayList<City>();
                      City citytmp = new City();
                      citytmp.setId(1);
                      citytmp.setName("beijing");
                      allCity.add(citytmp);
                      citytmp = new City();
                      citytmp.setId(2);
                      citytmp.setName("qingdao");                      
                      
                      Map<Integer, City> cityMap = new HashMap<>();
                      for (City city : allCity) {
                          cityMap.put(city.getId(), city);
                      }
                      return cityMap;
                  }
              });

      public void refresh() {
          cache.refresh(CACHE_KEY);
      }

      
      private Map<Integer, City> getCityMap() {
          try {
			return cache.get(CACHE_KEY);
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
      }

      public Collection<City> getAllCity() {
          return getCityMap().values();
      }

      public City getCityById(int cityId) {
          return getCityMap().get(cityId);
      }

      public String getCityName(int cityId) {
          City city = getCityById(cityId);
          return null == city ? "" : city.getName();
      }	
	
}

测试类:

package com.daojia.guava.cache;

public class CacheTest {

	public static void main(String[] args) {		

		CacheUtil cacheUtil = new CacheUtil();

		for (int i = 0; i < 3; i++) {
			System.out.println("vist num--- " + i + " ---");
			String cname = cacheUtil.getCityName(1);
			System.out.println("name:" + cname);
		}

	}

}

结果输出:

vist num--- 0 ---
load city from service:
name:beijing
vist num--- 1 ---
name:beijing
vist num--- 2 ---
name:beijing

可以看出第一次是模拟调用服务,后面的都是从缓存获取。

猜你喜欢

转载自blog.csdn.net/bohu83/article/details/83509811