Skip to main content
  1. posts/

Guaua Cache原理分析

·794 words·2 mins

什么是LocalCache

LocalCache见名知意,就是本地缓存,通过本地缓存一些数据,提升应用程序的访问性能。其中Java中一个比较好的实现框架就是guaua的LocalCache,底层是借用了jvm的堆内存来存放数据的

特性

本地缓存的实现,其实用concurrentMap也能实现,那guaua有什么优点呢?

  1. 过期处理
  2. 自动加载
  3. 并发控制,最多只会有一个线程去加载数据
  4. 配置回收策略
  5. 统计命中率

使用场景

  1. 热点数据访问,通过多级数据缓存提升访问性能
  2. 本地热点数据,或者重复耗时计算的逻辑产生的数据

基本使用

//定义一个本地缓存
LoadingCache<String, Optional<String>> cache = CacheBuilder.newBuilder()
                        .maximumSize(1000)
                        .expireAfterWrite(10L, java.util.concurrent.TimeUnit.SECONDS)
                        .concurrencyLevel(Runtime.getRuntime().availableProcessors())
                        .weakKeys()
                        .weakValues()
                        .build(new CacheLoader<String, Optional<String>>() {
                            //重写一个CacheLoader来实现加载方法
                            @Override
                            public Optional<String> load(String key) throws Exception {
                                System.out.println(key+" load success");
                                return Optional.of("key - "+ key +" - value");
                            }
                        });
//从缓存中获取数据
 try {
      //get方法需要处理为null的异常
      System.out.println(cache.get("key99999").orElseGet(()->"null"));
  } catch (ExecutionException e) {
      e.printStackTrace();
  }
  //不用处理异常,但是如果cache中不存在数据,不会触发加载逻辑
  cache.getIfPresent("key99999");

底层原理分析

整体流程

LocalCache

LoadingCache的核心在于get,追踪get源码,会发现大部分逻辑都在LocalCache类中。

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    int hash = hash(checkNotNull(key));
    //找到对应的segment,然后从segment中获取数据
    return segmentFor(hash).get(key, hash, loader);
}

public @Nullable V getIfPresent(Object key) {
int hash = hash(checkNotNull(key));
V value = segmentFor(hash).get(key, hash);
if (value == null) {
    globalStatsCounter.recordMisses(1);
} else {
    globalStatsCounter.recordHits(1);
}
return value;
}

Segment

@CanIgnoreReturnValue
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
      ReferenceEntry<K, V> e = getEntry(key, hash);
      if (e != null) {
        long now = map.ticker.read();
        V value = getLiveValue(e, now);
        if (value != null) {
          recordRead(e, now);
          statsCounter.recordHits(1);
          return scheduleRefresh(e, key, hash, value, now, loader);
        }
        ValueReference<K, V> valueReference = e.getValueReference();
        //如果引用对象正在加载中,等待加载完成再获取
        if (valueReference.isLoading()) {
          return waitForLoadingValue(e, key, valueReference);
        }
      }
    }
    //加锁获取数据
    // at this point e is either null or expired;
    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();
  }
}

存储结构

LocalCache 和 ConcurrentMap的区别

功能特性底层实现
LocalCache缓存1. 缓存过期 2. 自动加载 3. 并发控制 4. 命中率统计显式Segment分段锁机制加锁
ConcurrentMap并发k-v存储1. 并发控制CAS+ synchronized 对node节点加锁