Guava Cache

文章目录

  • 缓存使用场景
  • 缓存类型
  • Local Cache
    • Google Guava Cache
      • 优点
      • 适用场景
      • 内存结构
      • 常用方法
        • 创建
        • 加载
        • 缓存回收
        • 刷新
        • 过期
        • 统计
        • 移除监听器
        • 获取cache
  • 附录

缓存使用场景

计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。

缓存类型

  • 分布式缓存
  • 本地缓存(local cache) / 内存缓存(memory cache)

Local Cache

  • Google guava cache: 内存缓存框架
  • Encache: 不仅仅是内存缓存,还支持硬盘缓存
  • Oscache: 常用来缓存页面

Google Guava Cache

优点

  • 线程安全的本地缓存
  • 很好的封装了get、put操作,能够集成数据源。
  • 过期时间,元素失效
  • 回收机制
  • 监控缓存加载/命中等

适用场景

  • 多线程下内存缓存

内存结构

类似于ConcurrentHashMap
Guava Cache_第1张图片

  • 队列用于缓存淘汰机制,适用LRU(最近最久未使用)最先淘汰。
  • ReferenceEntry 用于保存key-value

常用方法

创建

  • CacheLoader
LoadingCache cache = CacheBuilder.newBuilder()
               .maximumSize(1000)
               .expireAfterAccess(30L, TimeUnit.MILLISECONDS)
               .build(createCacheLoader());

  • Callable callback
Cache cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build();
  • 回收的参数
    • 大小的设置:
      • CacheBuilder.maximumSize(long)
      • CacheBuilder.weigher(Weigher)
      • CacheBuilder.maxumumWeigher(long)
    • 时间
      • expireAfterAccess
      • expireAfterWrite
    • 引用
      • CacheBuilder.weakKeys()
      • CacheBuilder.weakValues()
      • CacheBuilder.softValues()
    • 明确删除
      • invalidate(key)
      • invalidateAll(keys)
      • invalidateAll()
    • 删除监听器
      • CacheBuilder.removalListener(RemovalListener)

加载

  • 自动加载
    • 原则:“获取缓存-如果没有-则计算”[get-if-absent-compute]
    • 严格限制只有1个加载操作,防止缓存失效的瞬间大量请求穿透到后端引起雪崩效应
    • CacheLoader: 为整个cache设置统一的值来源
    • Callable callback:在get是指定callable方法,为不同的key指定不同的value方法。
// CacheLoader
LoadingCache cache = CacheBuilder.newBuilder()
               .maximumSize(1000)
               .expireAfterAccess(30L, TimeUnit.MILLISECONDS)
               .build(createCacheLoader());

public static com.google.common.cache.CacheLoader createCacheLoader() {
       return new com.google.common.cache.CacheLoader() {
           @Override
           public Employee load(String key) throws Exception {
			....
           }
       };
  }
// Callable

Cache cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build();

// 如果有,则返回;没有则调用callable方法取得value,写入缓存,然后返回。
	try {
		cache.get(key, new Callable() {
			@Override
			public Value call() throws AnyException {
			  return doThingsTheHardWay(key);
			}
		});
	} catch (ExecutionException e) {
		...
	}

  • 手动加载/手动插入
    • Cache.put(key, value)

缓存回收

  • 被动移除
    • 基于容量的回收:
      • CacheBuilder.maximumSize(long),接近最大条数,按照LRU移除最早的。
    • 定时回收
      • 根据某个键值对最后一次访问之后多少时间后移除:expireAfterAccess
      • 根据某个键值对被创建或值被替换后多少时间移除:expireAfterWrite
    • 基于引用回收
      • java的垃圾回收机制
  • 主动移除/显示移除
    • 单独移除: Cache.invalidate(key)
    • 批量移除: Cache.invalidateAll(keys)
    • 移除所有: Cache.invalidateAll()

清理什么时候发生?
CacheBuilder构建的缓存不会"自动"执行清理和回收工作,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。

如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。

如果缓存是高吞吐的,无需担心缓存的维护和清理等工作。如果缓存吞吐比较少,可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()。ScheduledExecutorService可以实现定时调度。

刷新

刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值。

如果刷新过程抛出异常,缓存将保留旧值。

  • LoadingCache.refresh(K) 在生成新的value的时候,旧的value依然会被使用。
  • CacheLoader.reload(K, V) 生成新的value过程中允许使用旧的value
  • 定时刷新可以让缓存项保持可用,缓存项只有在被检索时才会真正刷新CacheBuilder.refreshAfterWrite(long, TimeUnit)

Note: 更新线程调用load方法更新该缓存,其他请求线程返回该缓存的旧值。这样对于某个key的缓存来说,只会有一个线程被阻塞,用来生成缓存值,而其他的线程都返回旧的缓存值,不会被阻塞。

过期

  • expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
  • expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。
  • refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。

统计

  • CacheBuilder.recordStats()用来开启Guava Cache的统计功能。
  • Cache.stats()方法会返回CacheStats对象以提供如下统计信息:
    • hitRate():缓存命中率;
    • averageLoadPenalty():加载新值的平均时间,单位为纳秒;
    • evictionCount():缓存项被回收的总数,不包括显式清除。

移除监听器

缓存项被移除时的额外操作可以使用移除监听器。默认情况下,监听器方法是在移除缓存时同步调用的。为了防止同步模式下会拖慢正常的缓存请求,可以使用RemovalListeners.asynchronous(RemovalListener, Executor)把监听器装饰为异步操作。

获取cache

对于get(key, loader)方法流程:

  • 对key做hash,找到存储的segment及数组table上的位置;
  • 链表上查找entry,如果entry不为空,且value没有过期,则返回value,并刷新entry。
  • 若链表上找不到entry,或者value已经过期,则调用lockedGetOrLoad。
  • 锁住整个segment,遍历entry可能在的链表,查看数据是否存在是否过期,若存在则返回。若过期则删除(table,各种queue)。若不存在,则新建一个entry插入table。放开整个segment的锁。
  • 锁住entry,调用loader的reload方法,从数据源加载数据,然后调用storeLoadedValue更新缓存。
  • storeLoadedValue时,锁住整个segment,将value设置到entry中,并设置相关数据(入写入/访问队列,加载/命中数据等)。

附录

源码解析:http://www.cnblogs.com/java-zhao/p/5140878.html
Refresh和expire解析:https://blog.csdn.net/lzwglory/article/details/82747482

你可能感兴趣的:(技术)