guava cache的理解

为什么要用cache

在业务实现过程中,有些基本信息进行缓存化,以提升服务的性能,如降低服务的响应时延。有人可能会问题,现在已经有了分布式缓存,为什么还要用本地缓存,因为本地缓存可以减低分布式缓存的压力,同时,可以减少访问分布式缓存的网络损耗,性能更佳。
那是不是所有的缓存都可以使用本地缓存,如果对于单体应用来说,使用本地缓存完全可以,在方案设计过程中,尽量考虑到后续随着业务量的增加,需要进行实例的扩容,单纯的本地缓存,将来进行扩容时,如果需要实例间共享的数据,当前通过本地缓存,会造成业务流程出现异常,或者效率降低很多。
什么样的适用于本地缓存?变动小或者基本不变的数据。

如果要设计一个本地缓存需要考虑哪些?

  • 并发的读、写能力,否则,就会就整个服务改成了串行。
  • 线程安全,如果不是一个线程安全的,那就没有使用的意义了。
  • 容量控制,既然是本地缓存,就会占用内存空间,因此,必须得可控。
  • 淘汰策略,常用的LRU、LFU。
  • 删除策略,手工删除、定时删除、懒汉式删除。
  • 快速查找,缓存的目的,就是为了快,否则,存在的意义是什么。

guava cache如何实现的

使用方式

通过CacheBuilder构建需要使用的localCache,提供了两种不同形式的cache操作。

参数信息
// 用于控制segment的初始table的大小,如果不设置的话,默认值16
int initialCapacity = UNSET_INT;
// 用于控制并发,如果不设置,默认是4
int concurrencyLevel = UNSET_INT;
// 最大的entry数量,会转换成weight使用,这个方式很不错,因为这相当于是一个特殊的weight,即weight为1
long maximumSize = UNSET_INT;
// 最大weight值
long maximumWeight = UNSET_INT;
// 计算weight的方式
@Nullable Weigher<? super K, ? super V> weigher;
// key引用类型
@Nullable Strength keyStrength;
// value的引用类型
@Nullable Strength valueStrength;

// 写入后多久过期
@SuppressWarnings("GoodTime") // should be a java.time.Duration
long expireAfterWriteNanos = UNSET_INT;
// 最后一次访问后多久过期
@SuppressWarnings("GoodTime") // should be a java.time.Duration
long expireAfterAccessNanos = UNSET_INT;
// 刷新时间
@SuppressWarnings("GoodTime") // should be a java.time.Duration
long refreshNanos = UNSET_INT;
构造cache
  • 手工操作的localCache
Cache<String, String> manualCache = CacheBuilder.newBuilder().build();
  • 带有加载机制的localCache
LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {
    @Override
    public String load(String key) throws Exception {
        return key;
    }
});

两者的区别,以get方法为例说明:
LocalManualCache中关于get的方法,需要传入一个valueLoader,起到的作用和LocalLoadingCache创建时,传入的CacheLoader作用一样,均是要在当前key在缓存中不存在时,重新加载该key对应的value值。

public V get(K key, final Callable<? extends V> valueLoader) throws ExecutionException {
  checkNotNull(valueLoader);
  return localCache.get(
      key,
      new CacheLoader<Object, V>() {
        @Override
        public V load(Object key) throws Exception {
          return valueLoader.call();
        }
      });
}

LocalLoadingCache中的get方法。

public V get(K key) throws ExecutionException {
  return localCache.getOrLoad(key);
}

以上两个方法均调用的事localCache的getOrLoad方法。

  V getOrLoad(K key) throws ExecutionException {
    return get(key, defaultLoader);
  }
  V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    int hash = hash(checkNotNull(key));
    // 这里是通过hash先确定segment
    return segmentFor(hash).get(key, hash, loader);
  }

接下来看Segment的get方法的具体实现

V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
  checkNotNull(key);
  checkNotNull(loader);
  try {
    // 确认当前有值
    if (count != 0) { // read-volatile
      // don't call getLiveEntry, which would ignore loading values
      // 查询当前对应的entry
      ReferenceEntry<K, V> e = getEntry(key, hash);
      if (e != null) {
        long now = map.ticker.read();
        // 获取当前entry对应的value内容,可能为null
        V value = getLiveValue(e, now);
        if (value != null) {
          // 记录当前已经读了,更新访问时间
          recordRead(e, now);
          statsCounter.recordHits(1);
          // 如果需要刷新value的,则进行刷新。
          return scheduleRefresh(e, key, hash, value, now, loader);
        }
        // 如果为null的话,判断是否是正在加载
        ValueReference<K, V> valueReference = e.getValueReference();
        if (valueReference.isLoading()) {
          // 等待加载结果
          return waitForLoadingValue(e, key, valueReference);
        }
      }
    }

    // at this point e is either null or expired;
    // 如果key已经过期或者本身就不存在,则重新加载对应的value.
    return lockedGetOrLoad(key, hash, loader);
  } catch (ExecutionException ee) {
    Throwable cause = ee.getCause();
    if (cause instanceof Error) {
      throw new ExecutionError((Error) cause);
    } else if (cause instanceof RuntimeException) {
      throw new UncheckedExecutionException(cause);
    }
    throw ee;
  } finally {
    postReadCleanup();
  }
}
关键所在

整个cache的关键,我个人理解在segment,提供了并发的能力。这个和JDK1.7的ConcurrentHashMap的设计理念很像,单个segment加锁,提高了并发能力。Segment底层实现依赖于AtomicReferenceArray来记录entry的引用信息。同时,也提供了扩容机制。这点和Map的实现机制基本一致,所以,在快速检索上,是不存在问题。

guava cache提供的删除策略有哪些

  • 基于写入时间淘汰
  • 基于访问时间淘汰
  • 基于LRU方式淘汰
  • 可以手工删除
  • 可以通过软引用或者弱引用的方式被垃圾回收

JVM的引用分类

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

通过以上分析,至少明白了软引用和弱引用的一种使用场景,就是在本地内存中。
关于guava的强引用验证

package com.google.common.cache;


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

/**
 * @author woniu
 * @date 2022/5/29 20:20
 **/
public class TestLocalCache {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        Cache<String, String> manualCache = CacheBuilder.newBuilder().build();
            LoadingCache<String, Data> loadingCache = CacheBuilder.newBuilder().build(new CacheLoader<String, Data>() {
            @Override
            public Data load(String key) throws Exception {
                return new Data(key);
            }
        });

        for (int i = 0; i < 100000; i++) {
            loadingCache.put(String.valueOf(i), new Data(String.valueOf(i)));
            TimeUnit.MILLISECONDS.sleep(600);
        }

        System.out.println(loadingCache.get("1"));

    }

    static class Data {
        private byte[] value = new byte[1024 * 1024];

        private String  index;

        public Data(String index) {
            System.out.println("new Data: " + index);
            this.index = index;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("this data " + index + " will be gc");
        }

        @Override
        public String toString() {
            return "index: " + index;
        }
    }
}

运行大约115次后,出现了内存溢出。因为是强引用,所以无法进行垃圾回收。
软引用案例
上述代码不变,仅在创建对象时添加参数。如下

LoadingCache<String, Data> loadingCache = CacheBuilder.newBuilder().softValues().build(new CacheLoader<String, Data>() {
    @Override
    public Data load(String key) throws Exception {
        return new Data(key);
    }
});

这样,就会发现,在进行FullGC时会进行对象的垃圾回收。
弱引用案例

LoadingCache<String, Data> loadingCache = CacheBuilder.newBuilder().weakValues().build(new CacheLoader<String, Data>() {
    @Override
    public Data load(String key) throws Exception {
        return new Data(key);
    }
});

就会发现在每次GC时,都会有一批对象被回收。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值