本地缓存Caffeine的使用

1 Caffeine介绍

Caffeine是一个高性能的本地缓存框架,缓存存储底层依赖JDK的ConcurrentHashMap。支持设置缓存过期时间等功能。

2 依赖


	com.github.ben-manes.caffeine
	caffeine
	2.9.2

3 应用

3.1 创建缓存实例

下面是创建缓存实例举例。

/**
 * 创建Caffeine缓存实例
 */
@Configuration
public class CaffeineConfig {
    /**
     * 通用Caffeine缓存的过期时间。单位:s.
     */
    @Value("${expireTime.caffeineCacheOfCommon:5}")
    private Integer expireTime4CaffeineCacheOfCommon;

    @Bean("caffeineCacheOfCommon")
    public Cache caffeineCacheOfCommon() {
        return Caffeine.newBuilder()
                // 设置创建缓存或者最后一次更新缓存后,经过固定时间后数据过期
                .expireAfterWrite(expireTime4CaffeineCacheOfCommon, TimeUnit.SECONDS)
                .build();
    }
    
}

3.2 使用

@Resource
private TestDubboService testDubboService;

@Resource
private Cache caffeineCacheOfCommon;
	
	
public List queryDto(String province) {
	if (StringUtils.isBlank(province)) {
		return new ArrayList<>();
	}

	try {
		List testList = (List) caffeineCacheOfCommon.getIfPresent(province);
		if (testList != null) {
			return testList;
		}

		
		testList = testDubboService.queryDtoByProvince(province);
		if (testList == null) {
			return new ArrayList<>();
		}

		caffeineCacheOfCommon.put(province, testList);
		return testList;
	} catch (Exception e) {
		log.error("queryDto error.", e);
	}

	return new ArrayList<>();
}

4 缓存过期策略的实现细节

4.1 基于时间的过期策略

获取缓存数据时,根据当前时间、缓存写入时间(或缓存访问时间、截止时间)和缓存过期时间,判断缓存是否过期。如果已过期,则异步执行过期缓存删除任务。

// 获取缓存数据
public @Nullable V getIfPresent(Object key, boolean recordStats) {
Node node = data.get(nodeFactory.newLookupKey(key));
	if (node == null) {
	  if (recordStats) {
		statsCounter().recordMisses(1);
	  }
	  if (drainStatus() == REQUIRED) {
		scheduleDrainBuffers();
	  }
	  return null;
	}

	V value = node.getValue();
	long now = expirationTicker().read();
	
	// 判断缓存是否过期或是否已被垃圾回收
	if (hasExpired(node, now) || (collectValues() && (value == null))) {
	  if (recordStats) {
		statsCounter().recordMisses(1);
	  }
	  
	  // 异步执行过期缓存删除任务
	  scheduleDrainBuffers();
	  return null;
	}

	if (!isComputingAsync(node)) {
	  @SuppressWarnings("unchecked")
	  K castedKey = (K) key;
	  setAccessTime(node, now);
	  tryExpireAfterRead(node, castedKey, value, expiry(), now);
	}
	afterRead(node, now, recordStats);
	return value;
}

// 1、判断缓存是否过期
// now-当前时间,node.getWriteTime-缓存写入时间,expiresAfterWriteNanos-缓存过期时间
boolean hasExpired(Node node, long now) {
return (expiresAfterAccess() && (now - node.getAccessTime() >= expiresAfterAccessNanos()))
	| (expiresAfterWrite() && (now - node.getWriteTime() >= expiresAfterWriteNanos()))
	| (expiresVariable() && (now - node.getVariableTime() >= 0));
}


// 2、异步执行过期缓存删除任务。drainBuffersTask为PerformCleanupTask
void scheduleDrainBuffers() {
	if (drainStatus() >= PROCESSING_TO_IDLE) {
	  return;
	}
	if (evictionLock.tryLock()) {
	  try {
		int drainStatus = drainStatus();
		if (drainStatus >= PROCESSING_TO_IDLE) {
		  return;
		}
		lazySetDrainStatus(PROCESSING_TO_IDLE);
		executor.execute(drainBuffersTask);
	  } catch (Throwable t) {
		logger.log(Level.WARNING, "Exception thrown when submitting maintenance task", t);
		maintenance(/* ignored */ null);
	  } finally {
		evictionLock.unlock();
	  }
	}
}

public void run() {
  BoundedLocalCache cache = reference.get();
  if (cache != null) {
	cache.performCleanUp(/* ignored */ null);
  }
}
	
void performCleanUp(@Nullable Runnable task) {
	evictionLock.lock();
	try {
	  maintenance(task);
	} finally {
	  evictionLock.unlock();
	}
	if ((drainStatus() == REQUIRED) && (executor == ForkJoinPool.commonPool())) {
	  scheduleDrainBuffers();
	}
}

void maintenance(@Nullable Runnable task) {
	lazySetDrainStatus(PROCESSING_TO_IDLE);

	try {
	  drainReadBuffer();

	  drainWriteBuffer();
	  if (task != null) {
		task.run();
	  }

	  drainKeyReferences();
	  drainValueReferences();

	  expireEntries();
	  evictEntries();

	  climb();
	} finally {
	  if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) {
		lazySetDrainStatus(REQUIRED);
	  }
	}
}

5 新增缓存的实现细节

下面以 BoundedLocalCache#put 的方法为例阐述新增缓存的实现细节。主要步骤包括:

(1)创建node;

(2)新增缓存。

使用ConcurrentHashMap存储缓存信息。主要代码如下所示。

ConcurrentHashMap> data
public V put(K key, V value) {
	return this.put(key, value, this.expiry(), true, false);
}


V put(K key, V value, Expiry expiry, boolean notifyWriter, boolean onlyIfAbsent) {
	Objects.requireNonNull(key);
	Objects.requireNonNull(value);
	Node node = null;
	long now = this.expirationTicker().read();
	int newWeight = this.weigher.weigh(key, value);

	Node prior;
	Object oldValue;
	int oldWeight;
	boolean expired;
	boolean mayUpdate;
	boolean withinTolerance;
	while(true) {
		prior = (Node)this.data.get(this.nodeFactory.newLookupKey(key));
		if (prior == null) {
			if (node == null) {
				// 1、创建node
				node = this.nodeFactory.newNode(key, this.keyReferenceQueue(), value, this.valueReferenceQueue(), newWeight, now);
				this.setVariableTime(node, this.expireAfterCreate(key, value, expiry, now));
			}

			if (notifyWriter && this.hasWriter()) {
				prior = (Node)this.data.computeIfAbsent(node.getKeyReference(), (k) -> {
					this.writer.write(key, value);
					return node;
				});
				if (prior == node) {
					this.afterWrite(new BoundedLocalCache.AddTask(node, newWeight));
					return null;
				}
			} else {
				// 2、新增缓存
				prior = (Node)this.data.putIfAbsent(node.getKeyReference(), node);
				if (prior == null) {
					this.afterWrite(new BoundedLocalCache.AddTask(node, newWeight));
					return null;
				}
			}
		} else if (onlyIfAbsent) {
			oldValue = prior.getValue();
			if (oldValue != null && !this.hasExpired(prior, now)) {
				if (!this.isComputingAsync(prior)) {
					this.tryExpireAfterRead(prior, key, oldValue, this.expiry(), now);
					this.setAccessTime(prior, now);
				}

				this.afterRead(prior, now, false);
				return oldValue;
			}
		}

		expired = false;

	// ...

	return expired ? null : oldValue;
}

6 参考文献

(1)java缓存框架Caffeine详解

你可能感兴趣的:(本地缓存,缓存,caffeine)