CacheBuilderr

/ 技术 / 无站内评论 / 229浏览
CacheBuilder
CacheBuilder是缓存配置和构建入口,先看一些属性。CacheBuilder的设置操作都是为这些属性赋值。
    //缓存的默认初始化大小
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    // LocalCache默认并发数,用来评估Segment的个数
    private static final int DEFAULT_CONCURRENCY_LEVEL = 4;
    //默认的缓存过期时间
    private static final int DEFAULT_EXPIRATION_NANOS = 0;
    
    static final int UNSET_INT = -1;
    int initialCapacity = UNSET_INT;//初始缓存大小
    int concurrencyLevel = UNSET_INT;//用于计算有几个并发
    long maximumSize = UNSET_INT;//cache中最多能存放的缓存entry个数
    long maximumWeight = UNSET_INT;
    
    Strength keyStrength;//键的引用类型(strong、weak、soft)
    Strength valueStrength;//值的引用类型(strong、weak、soft)

    long expireAfterWriteNanos = UNSET_INT;//缓存超时时间(起点:缓存被创建或被修改)
    long expireAfterAccessNanos = UNSET_INT;//缓存超时时间(起点:缓存被创建或被修改或被访问)
    //元素被移除的监听器
     RemovalListener<? super K, ? super V> removalListener;
     //状态计数器,默认为NULL_STATS_COUNTER,即不启动计数功能
     Supplier<? extends StatsCounter> statsCounterSupplie
CacheBuilder构建缓存有两个方法:
//构建一个具有数据加载功能的缓存
  public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
      CacheLoader<? super K1, V1> loader) {
    checkWeightWithWeigher();
    //调用LocalCache构造方法
    return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);
  }
  
  //构建一个没有数据加载功能的缓存
   public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
    checkWeightWithWeigher();
    checkNonLoadingCache();
    //调用LocalCache构造方法,但loader为null
    return new LocalCache.LocalManualCache<K1, V1>(this);
  }

 //被CacheBuilder的build方法调用,将其参数传递给LocalCache
  LocalCache(
      CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
    //默认并发水平是4,即四个Segment(但要注意concurrencyLevel不一定等于Segment个数)
    //Segment个数:一个刚刚大于或等于concurrencyLevel且是2的几次方的一个数,在后面会有segmentCount赋值过程    
    concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);

    keyStrength = builder.getKeyStrength();//默认为Strong,即强引用
    valueStrength = builder.getValueStrength();//默认为Strong,即强引用

    keyEquivalence = builder.getKeyEquivalence();
    valueEquivalence = builder.getValueEquivalence();

    maxWeight = builder.getMaximumWeight();
    weigher = builder.getWeigher();
    expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
    expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
    refreshNanos = builder.getRefreshNanos();

    removalListener = builder.getRemovalListener();
    removalNotificationQueue = (removalListener == NullListener.INSTANCE)
        ? LocalCache.<RemovalNotification<K, V>>discardingQueue()
        : new ConcurrentLinkedQueue<RemovalNotification<K, V>>();

    ticker = builder.getTicker(recordsTime());
    entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
    globalStatsCounter = builder.getStatsCounterSupplier().get();
    defaultLoader = loader;

    int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
    if (evictsBySize() && !customWeigher()) {
      initialCapacity = Math.min(initialCapacity, (int) maxWeight);
    }

   //调整segmentCount个数,通过位移实现,所以是2的n次方。
    int segmentShift = 0;
    int segmentCount = 1;
    while (segmentCount < concurrencyLevel
           && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
      ++segmentShift;
      segmentCount <<= 1;
    }
    this.segmentShift = 32 - segmentShift;
    segmentMask = segmentCount - 1;
    //初始化segments
    this.segments = newSegmentArray(segmentCount);
    //每个segment的大小
    int segmentCapacity = initialCapacity / segmentCount;
    if (segmentCapacity * segmentCount < initialCapacity) {
      ++segmentCapacity;
    }

    int segmentSize = 1;
    while (segmentSize < segmentCapacity) {
      segmentSize <<= 1;
    }
    //初始化Segments
    if (evictsBySize()) {
      // Ensure sum of segment max weights = overall max weights
      long maxSegmentWeight = maxWeight / segmentCount + 1;
      long remainder = maxWeight % segmentCount;
      for (int i = 0; i < this.segments.length; ++i) {
        if (i == remainder) {
          maxSegmentWeight--;
        }
        this.segments[i] =
            createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
      }
    } else {
      for (int i = 0; i < this.segments.length; ++i) {
        this.segments[i] =
            createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get());
      }
    }
  }

    //Segment初始化操作,结构与上面图中大致相同(图中省去部分队列)
    Segment(LocalCache<K, V> map, int initialCapacity, long maxSegmentWeight,
        StatsCounter statsCounter) {
      this.map = map;
      this.maxSegmentWeight = maxSegmentWeight;
      this.statsCounter = checkNotNull(statsCounter);
      //初始化table
      initTable(newEntryArray(initialCapacity));
     //key引用队列
      keyReferenceQueue = map.usesKeyReferences()
           ? new ReferenceQueue<K>() : null;
     //value引用队列
      valueReferenceQueue = map.usesValueReferences()
           ? new ReferenceQueue<V>() : null;

      recencyQueue = map.usesAccessQueue()
          ? new ConcurrentLinkedQueue<ReferenceEntry<K, V>>()
          : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
      //写入元素队列
      writeQueue = map.usesWriteQueue()
          ? new WriteQueue<K, V>()
          : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
     //访问元素队列
      accessQueue = map.usesAccessQueue()
          ? new AccessQueue<K, V>()
          : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
    }
LocalCache
LocalCache是guava cache的核心类。LocalCache的构造函数在上面已经分析过,接着看下核心方法。
对于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中,并设置相关数据(入写入/访问队列,加载/命中数据等)。
getAll(keys)方法:
循环调用get方法,从缓存中获取key对应的value。没有命中的记录下来。
如果有没有命中的key,调用loadAll(keys,loader)方法加载数据。
将加载的数据依次缓存,调用segment的put(K key, int hash, V value, boolean onlyIfAbsent)方法。
put时,锁住整个segment,将数据插入链表,更新统计数据。
put(key,value)方法:
对key做hash,找到segment的位置和table上的位置;
锁住整个segment,将数据插入链表,更新统计数据。
putAll(map) 循环调用put方法。
putIfAbsent(key, value) 缓存中,键值对不存在的时候才插入。
实践
guava cache是将数据源中的数据缓存在本地,那如果我们想把远端数据源中的数据缓存在远端 分布式缓存(如redis),可以怎么来使用guava cache的方式进行封装呢?
可以仿照guava写一个简单的缓存,定义如下:
CacheBuilder类 : 配置缓存参数,构建缓存。同上面所讲。
Cache接口:定义增删查接口。
MyCache类:实现Cache接口,put -> 存入DB,更新缓存; get -> 查询缓存,存在即返回;若不存在,查询DB,更新缓存,返回。
CacheLoader类:供MyCache调用,get和getAll时提供单次查DB和批量查DB。
参考
Google guava cache源码解析1--构建缓存器
Google Guava 缓存
后续
guava cache基于引用回收相关;
删除监听器相关。
召唤蕾姆
琼ICP备18000156号

鄂公网安备 42011502000211号