Guava Cache 初步学习

Guava Cache 是什么?

1.cache 就是为了提升系统性能而开辟的一块内存空间;

2.缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存;

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

3.日常开发的很多场合,由于受限于硬盘IO的性能或者我们自身业务系统的数据处理和获取可能非常费时,当我们发现我们的系统这个数据请求量很大的时候,频繁的IO和频繁的逻辑处理会导致硬盘和CPU资源的瓶颈出现。缓存的作用就是将这些来自不易的数据保存在内存中,当有其他线程或者客户端需要查询相同的数据资源时,直接从缓存的内存块中返回数据,这样不但可以提高系统的响应时间,同时也可以节省对这些数据的处理流程的资源消耗,整体上来说,系统性能会有大大的提升;

4.缓存在很多系统和架构中都用广泛的应用,例如:

  • CPU缓存
  • 操作系统缓存
  • 本地缓存
  • 分布式缓存
  • HTTP缓存
  • 数据库缓存

5.Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。

6. CacheBuilder作为LoadingCache 与 Cache实例的创建者,具有以下特征:

       1、自动载入键值至缓存;

       2、当缓存器溢出时,采用最近最少使用原则进行替换。

       3、过期规则可基于最后读写时间。

       4、设置键值引用级别。

       5、元素移出通知。

       6、缓存访问统计。


为什么使用?

  1. 你愿意花一些记忆来提高速度。You are willing to spend some memory to improve speed.
  2. 您希望Key有时会不止一次被查询。You expect that keys will sometimes get queried more than once.
  3. 你的缓存不需要存储更多的数据比什么都适合在。(Guava缓存是本地应用程序的一次运行)。Your cache will not need to store more data than what would fit inRAM. (Guava caches are local to a single run of your application.
  4. 它们不将数据存储在文件中,也不存储在外部服务器上。如果这样做不适合您的需要,考虑一个工具像memcached。

怎么实现?

Guava Cache有两种创建方式:

  • CacheLoader
  • Callable

这两种创建方式有什么异同呢?

两种方式同样按照获取缓存-如果没有-则计算(get-if-absent-compute)的缓存规则对缓存数据进行的处理的。

不同之处在于:在缓存中没有得到value的时候,CacheLoader会定义一个比较宽泛的、统一的统一的根据key值load value的方法,而Callablee的方式较为灵活,允许你在get的时候指定。

1.CacheLoader实现

	}

	/**
	 * maximumSize 如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。
	 * 缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。
	 * 
	 * expireAfterAccess 
	 * 定时回收(Timed Eviction) CacheBuilder 提供两种定时回收的方法:
	 * 
	 * expireAfterAccess(long,TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
	 * expireAfterWrite(long,TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
	 * 如下文所讨论,定时回收周期性地在写操作中执行,偶尔在读操作中执行。
	 * 
	 * cacheLoader 
	 * LoadingCache是附带CacheLoader构建而成的缓存实现。
	 * 创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法
	 * 
	 * @throws ExecutionException
	 * 
	 * 
	 */
	private static void cacheLoaderCreate() throws ExecutionException {
		LoadingCache<String, String> cacheBuilder = CacheBuilder.newBuilder().maximumSize(1000)
				.expireAfterAccess(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
					@Override
					public String load(String key) throws Exception {
						return "key:isSetting";
					}
				});

		String value = cacheBuilder.get("小强");
		System.out.println(value);
	}

2.Callable实现

/**
	 * 所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K,Callable)方法。
	 * 这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
	 * 在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式”如果有缓存则返回;否则运算、缓存、然后返回”。
	 * 
	 * @throws ExecutionException
	 * 
	 * 这个方法返回缓存中相应的值,或者用给定的 Callable
	 * 算并把结果加入到缓存中。在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。
	 * 
	 */
	private static void callableCreate() throws ExecutionException {
		Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(10, TimeUnit.MINUTES).build();
		String value = cache.get("xiaoqiang", new Callable<String>() {
			@Override
			public String call() throws Exception {
				return "xiaoqiang : 123456";
			}
			
		});
		
		System.out.println(value);
	}

3.定义一个工具类

 package com.xxq.qianniu.rest.framework.cache;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

public class GuavaClient {

	private static Log LOG = LogFactory.getLog(GuavaClient.class);

	private static GuavaClient guavaClient = null;
	
	private static Cache<String,Object> cache = null;

	public static GuavaClient getInstance() {
		try {
			guavaClient = new GuavaClient();
			return guavaClient;
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 * 通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器
	 * ,以便缓存项被移除时做一些额外操作。缓存项被移除时,
	 * RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。
	 * removalListener 监听器  缓存销毁之后要做的处理  比如 打印日志
	 * 
	 */
	private GuavaClient() {
		
		cache	= CacheBuilder.newBuilder().maximumSize(10000)
                .expireAfterWrite(24, TimeUnit.HOURS)
                .initialCapacity(10)
                .removalListener(new RemovalListener<String, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<String, Object> rn) {
                            LOG.info("被移除缓存{}:{}" + rn.getKey() + rn.getValue());
                    }
                }).build();

	}

	/**
	 * maximumSize 如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。
	 * 缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。
	 * 
	 * expireAfterAccess 
	 * 定时回收(Timed Eviction) CacheBuilder 提供两种定时回收的方法:
	 * 
	 * expireAfterAccess(long,TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
	 * expireAfterWrite(long,TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
	 * 如下文所讨论,定时回收周期性地在写操作中执行,偶尔在读操作中执行。
	 * 
	 * cacheLoader 
	 * LoadingCache是附带CacheLoader构建而成的缓存实现。
	 * 创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法
	 * 
	 * @throws ExecutionException
	 * 
	 * 
	 */
	private static void cacheLoaderCreate() throws ExecutionException {
		LoadingCache<String, String> cacheBuilder = CacheBuilder.newBuilder().maximumSize(1000)
				.expireAfterAccess(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
					@Override
					public String load(String key) throws Exception {
						return "key:isSetting";
					}
				});

		String value = cacheBuilder.get("小强");
		System.out.println(value);
	}
	
	/**
	 * 所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K,Callable)方法。
	 * 这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
	 * 在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式”如果有缓存则返回;否则运算、缓存、然后返回”。
	 * 
	 * @throws ExecutionException
	 * 
	 * 这个方法返回缓存中相应的值,或者用给定的 Callable
	 * 算并把结果加入到缓存中。在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。
	 * 
	 */
	private static void callableCreate() throws ExecutionException {
		Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(10, TimeUnit.MINUTES).build();
		String value = cache.get("xiaoqiang", new Callable<String>() {
			@Override
			public String call() throws Exception {
				return "xiaoqiang : 123456";
			}
			
		});
		
		System.out.println(value);
	}

	public static void main(String[] args) throws ExecutionException {
		cacheLoaderCreate();
		callableCreate();
	}
	
	 
    /**
     **返回这个缓存中与key关联的值,如果没有,返回null
     *而且不会引起缓存项的加载。  不会重新加载创建cache
     * @param key
     * @return
     */
    public  static Object get(String key) {
        return StringUtils.isEmpty(key) ?  null  : cache.getIfPresent(key);
    }

	/**
	 * 缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选的,因为它可以更容易地推断所有缓存内容的一致性。
	 * 
	 * 使用cache.put(key,value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。进一步说,asMap视图的原子运算在Guava
	 * Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable<V>)
	 * 应该总是优先使用
	 * 
	 * @param key
	 * @param value
	 */
	public static void put(String key, Object value) {
		if (!StringUtils.isEmpty(key) && value != null) {
			cache.put(key, value);
		}
	}
	
	  
    /**
     * 使其失效
     * @param key
     */
    public static void remove(String key){
        if(!StringUtils.isEmpty(key)){
            cache.invalidate(key);
        }
    }
   
    /**
     * 批量失效
     * @param keys
     */
    public static void remove(List<String> keys){
        if(keys !=null && keys.size() >0){
            cache.invalidateAll(keys);
        }
    }

}

 4.属性

  •    maximumSize(long):设置容量大小,超过就开始回收。
  •     expireAfterAccess(long, TimeUnit):在这个时间段内没有被读/写访问,就会被回收。
  •     expireAfterWrite(long, TimeUnit):在这个时间段内没有被写访问,就会被回收 。
  •     removalListener(RemovalListener):监听事件,在元素被删除时,进行监听。

4.缓存回收

  • 基于容量回收

规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)

  • 定时回收

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

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

  不知道 

显式清除

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

  • 个别清除:Cache.invalidate(key)
  • 批量清除:Cache.invalidateAll(keys)
  • 清除所有缓存项:Cache.invalidateAll()

监听器:

 
	/**
	 * 
	 * 通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器
	 * ,以便缓存项被移除时做一些额外操作。缓存项被移除时,
	 * RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。
	 * removalListener 监听器  缓存销毁之后要做的处理  比如 打印日志
	 * 
	 */
	private GuavaClient() {
		
		cache	= CacheBuilder.newBuilder().maximumSize(10000)
                .expireAfterWrite(24, TimeUnit.HOURS)
                .initialCapacity(10)
                .removalListener(new RemovalListener<String, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<String, Object> rn) {
                            LOG.info("被移除缓存{}:{}" + rn.getKey() + rn.getValue());
                    }
                }).build();

	}

刷新

刷新和回收不太一样。正如 LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。
如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃。

  • 重载 CacheLoader.reload(K, V)可以扩展刷新时的行为,这个方法允许开发者在计算新值时使用旧的值。
  • CacheBuilder.refreshAfterWrite(long, TimeUnit)可以为缓存增加自动定时刷新功能。和 expireAfterWrite相反,refreshAfterWrite 通过定时刷新可以让缓存项保持可用,但请注意:缓存项只有在被检索时才会真正刷新(如果 CacheLoader.refresh 实现为异步,那么检索不会被刷新拖慢)。因此,如果你在缓存上同时声明 expireAfterWrite 和 refreshAfterWrite,缓存并不会因为刷新盲目地定时重置,如果缓存项没有被检索,那刷新就不会真的发生,缓存项在过期时间后也变得可以回收。

参考资料:

https://www.jianshu.com/p/b3c10fcdbf0f

https://blog.csdn.net/congcong68/article/details/41146295

https://blog.csdn.net/u012881904/article/details/79263787

https://blog.csdn.net/abc86319253/article/details/53020432

https://blog.csdn.net/Desilting/article/details/11768773

https://blog.csdn.net/xlgen157387/article/details/47293517


 

猜你喜欢

转载自blog.csdn.net/qq_32363305/article/details/82596753