添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
知识渊博的花卷  ·  C# ...·  1 年前    · 
长情的火锅  ·  Java 解决: ...·  1 年前    · 

一、学习目标

1、掌握Caffeine的3种填充策略:

  • 手动填充数据
  • 同步加载
  • 异步加载

2、掌握Caffeine提供了3种回收策略:

  • 基于大小回收
  • 基于时间回收
  • 基于引用回收

3、Caffeine的移除监听器

如果我们需要在缓存被移除的时候,得到通知产生回调,并做一些额外处理工作。这个时候RemovalListener就派上用场了。

删除侦听器的里面的操作是使用Executor来异步执行的。默认执行程序是ForkJoinPool.commonPool(),可以通过Caffeine.executor(Executor)覆盖。

4、Caffeine的CacheWriter

CacheWriter允许缓存充当一个底层资源的代理,当与CacheLoader结合使用时,所有对缓存的读写操作都可以通过Writer进行传递。Writer可以把操作缓存和操作外部资源扩展成一个同步的原子性操作。并且在缓存写入完成之前,它将会阻塞后续的更新缓存操作,但是读取(get)将直接返回原有的值。 如果写入程序失败,那么原有的key和value的映射将保持不变,如果出现异常将直接抛给调用者。

能监听到的动作:

  • CacheWriter可以同步的监听到缓存的创建、变更和删除操作。

不能监听到的动作:

  • 加载(例如,LoadingCache.get)、重新加载(例如,LoadingCache.refresh)和计算(例如Map.computeIfPresent)的操作不被CacheWriter监听到。

注意事项:

  • CacheWriter不能与弱键或AsyncLoadingCache一起使用。

应用场景:

  • 可以配合外部存储使用

5、Caffeine的统计

6、Caffeine的刷新

  • 刷新并不是到期就刷新,而是对这个数据再次访问之后,才会刷新。只阻塞加载数据的线程,其余线程返回旧数据。

二、Caffeine简介

Caffeine是基于Java8的高性能缓存库,可提供接近最佳的命中率。Caffeine的底层使用了ConcurrentHashMap,支持按照一定的规则或者自定义的规则使缓存的数据过期,然后销毁。

Caffeine的特性:

  • 自动把数据加载到本地缓存中,并且可以配置异步;
  • 淘汰策略:基于数量剔除策略;基于失效时间剔除策略,这个时间是从最后一次操作算起【访问或者写入】;
  • 异步刷新;
  • Key会被包装成Weak引用;Value会被包装成Weak或者Soft引用,从而能被GC掉,而不至于内存泄漏;
  • 数据剔除提醒;
  • 写入广播机制;
  • 缓存访问可以统计;

Caffeine的内部结构:

  • Cache的内部包含着一个ConcurrentHashMap:这也是存放我们所有缓存数据的地方,众所周知,ConcurrentHashMap是一个并发安全的容器,这点很重要,可以说Caffeine其实就是一个被强化过的ConcurrentHashMap。
  • Scheduler:定期清空数据的一个机制,可以不设置,如果不设置则不会主动的清空过期数据。
  • Executor:指定运行异步任务时要使用的线程池。可以不设置,如果不设置则会使用默认的线程池,也就是ForkJoinPool.commonPool()。

三、Caffeine的3种填充策略

Caffeine提供了3种填充策略:手动填充数据,同步加载,异步加载。

1、手动填充数据:

public class CaffeineManualTest {	@Testpublic void test() {		// 初始化缓存,设置了1分钟的写过期,100的缓存最大个数Cache<Integer, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.MINUTES)
                .maximumSize(100)
                .build();int key1 = 1;// getIfPresent(Object key):根据key从缓存中获取值,如果没有返回NULLSystem.out.println(cache.getIfPresent(key1));// get(key, Function):根据key查询一个缓存,如果没有将返回提供默认值,并保存到缓存// 通过cache.put(key, value)方法显示的将数控放入缓存,但是这样子会覆盖缓原来key的数据,更加建议使用cache.get(key,k - > value) 的方式System.out.println(cache.get(key1, new Function<Integer, String>() {@Overridepublic String apply(Integer key) {
                System.out.println(key);return "abcd";
        System.out.println(cache.getIfPresent(key1));// put(key, value):会覆盖缓原来key的数据String value1 = "wxyz";
        cache.put(key1, value1);
        System.out.println(cache.getIfPresent(key1));// invalidate(key):移除数据,让数据失效cache.invalidate(key1);
        System.out.println(cache.getIfPresent(key1));
}复制代码

如果同时有多个线程进行get,那么这个Function对象是否会被执行多次呢?

  • 实际上不会的,可以从结构图看出,Caffeine内部最主要的数据结构就是一个ConcurrentHashMap,而get的过程最终执行的便是ConcurrentHashMap.compute,这里仅会被执行一次。
  • get 方法是以阻塞方式执行调用,即使多个线程同时请求该值也只会调用一次Function方法。这样可以避免与其他线程的写入竞争,这也是为什么使用 get 优于 getIfPresent 的原因。

2、同步加载:

利用这个同步机制,也就是在CacheLoader对象中的load函数中,当从Caffeine缓存中取不到数据的时候,则从数据库中读取数据,将其插入缓存中。通过这个机制和数据库结合使用。

public class CaffeineLoadingTest {	/**
	 * 从数据库获取数据
	 * @param key
	 * @return
	 */private String getFromDataBase(int key) {		return key + RandomStringUtils.randomAlphabetic(8);
	}	@Testpublic void test() {		// 初始化缓存,设置了1分钟的写过期,100的缓存最大个数
		LoadingCache<Integer, String> cache = Caffeine.newBuilder()
				.expireAfterWrite(1, TimeUnit.MINUTES)
				.maximumSize(100)
				.build(						new CacheLoader<Integer, String>() {							/**
							 * 默认情况下,get、getAll将会对缓存中没有值的key分别调用CacheLoader.load方法来构建缓存的值。
							 * @param key
							 * @return
							 */@Overridepublic String load(@NonNull Integer key) {								return getFromDataBase(key);
							}							/**
							 * 在loadAll被重写的情况下,getAll将会对缓存中没有值的key分别调用CacheLoader.loadAll方法来构建缓存的值。
							 * @param keys
							 * @return
							 */@Overridepublic @NonNull Map<Integer, String> loadAll(@NonNull Iterable<? extends Integer> keys) {
								Map<Integer, String> map = new HashMap<>();								for (Integer key : keys) {
									map.put(key, getFromDataBase(key) + "abcd");
								}								return map;
				);		int key1 = 100;		// get(key, Function):根据key查询一个缓存,如果没有将返回提供默认值,并保存到缓存// 通过cache.put(key, value)方法显示的将数控放入缓存,但是这样子会覆盖缓原来key的数据,更加建议使用cache.get(key,k - > value) 的方式
		String value1 = cache.get(key1);
		System.out.println(value1);		//  getAll(keys):批量查找
		Map<Integer, String> dataMap = cache.getAll(Arrays.asList(1, 2, 3, 100));
		System.out.println(dataMap);
}复制代码

运行结果:

100FDQHepbj
{1=1UwoFIuYoabcd, 2=2eDQKMwqpabcd, 3=3HYnJXUsRabcd, 100=100FDQHepbj}复制代码

3、异步加载:

public class CaffeineAsynchronousTest {	/**
	 * 从数据库获取数据
	 * @param key
	 * @return
	 */private String getFromDataBase(int key) {
		System.out.println("2当前所在线程:" + Thread.currentThread().getName());		try {
			Thread.sleep(10_000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		return key + RandomStringUtils.randomAlphabetic(8);
	}	private CompletableFuture<String> getValue(Integer key) {
		System.out.println("2当前所在线程:" + Thread.currentThread().getName());		try {
			Thread.sleep(10_000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		return CompletableFuture.supplyAsync(() -> key + RandomStringUtils.randomAlphabetic(8));
	}	@Testpublic void test2() throws ExecutionException, InterruptedException {
		AsyncLoadingCache<Integer, String> asyncLoadingCache = Caffeine.newBuilder()
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.maximumSize(10_000)				// 使用executor设置线程池// .executor(Executors.newSingleThreadExecutor())
				.buildAsync(key -> getFromDataBase(key));		// .buildAsync(key -> getValue(key).get(1, TimeUnit.SECONDS));
		Integer key = 1;		// put(key, value):会覆盖缓原来key的数据// 此时不会执行setAsyncValue()、getFromDataBase()
		String value1 = "wxyz1";
		asyncLoadingCache.put(key, CompletableFuture.completedFuture(value1));
		System.out.println("1当前所在线程:" + Thread.currentThread().getName());
		CompletableFuture<String> future = asyncLoadingCache.get(key);
		String value = future.get();
		System.out.println(value);
}复制代码

四、Caffeine的3种回收策略

Caffeine提供了3种回收策略:基于大小回收,基于时间回收,基于引用回收。

1、基于大小回收:

基于最大缓存数量:

@Testpublic void test() throws InterruptedException {// 初始化缓存,设置了1分钟的写过期,2的缓存最大个数Cache<Integer, String> cache = Caffeine.newBuilder()
            .maximumSize(2)
            .build();
    cache.put(1, "a");
    System.out.println(cache.estimatedSize());
    cache.put(2, "b");
    System.out.println(cache.estimatedSize());
    System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2)));// 触发缓存回收(异步)cache.put(3, "c");
    System.out.println(cache.estimatedSize());
    System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2, 3)));// 触发缓存回收(异步)cache.put(4, "d");
    System.out.println(cache.estimatedSize());// 缓存回收是异步执行,cleanUp()可以等待异步执行完成// cache.cleanUp();Thread.sleep(1000);
    System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2, 3, 4)));
}复制代码

运行结果:

1
{1=a, 2=b}
{1=a, 2=b, 3=c}
{2=b, 4=d}复制代码

基于权重:

/**
 * 基于权重
 */@Testpublic void testMaximumWeight() {
    Cache<Integer, Integer> cache = Caffeine.newBuilder()// TODO 指定指定缓存最大权重值,配合weigher使用,随着缓存大小逐渐接近最大值,会回收最近或被很少使用的缓存,maximumWeight与maximumSize不能同时使用.maximumWeight(11)// 指定权重计算方式,缓存创建或更新时触发.weigher(new Weigher<Object, Object>() {@Overridepublic @NonNegative int weigh(@NonNull Object key, @NonNull Object value) {if (value instanceof Integer) {
                        System.out.println("是Integer类型的: " + value);return (Integer) value;
                    System.out.println("不是Integer类型的: " + value);return 0;
            .build();
    List<Integer> keys = new ArrayList<>();for (int i = 0; i < 100; i++) {
        cache.put(i, i);
        keys.add(i);
    Map<Integer, Integer> map = cache.getAllPresent(keys);
    map.forEach((k, v) -> System.out.println(k + ":" + v));
    cache.cleanUp();
    System.out.println();
    map = cache.getAllPresent(keys);
    map.forEach((k, v) -> System.out.println(k + ":" + v));int sum = 0;for (Integer ele : map.keySet()) {
        sum += ele;
    System.out.println("元素总和: " + sum );// Thread.sleep(1000);System.out.println(cache.estimatedSize());
}复制代码

运行结果:

是Integer类型的: 0
是Integer类型的: 1
是Integer类型的: 2
是Integer类型的: 3
是Integer类型的: 97
是Integer类型的: 98
是Integer类型的: 99
97:97
98:98
99:99
元素总和: 8
2复制代码

2、基于时间回收:

  • expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。
  • expireAfterWrite(long, TimeUnit):在最后一次写入缓存后开始计时,在指定的时间后过期。
  • expireAfter(Expiry):自定义策略,过期时间由Expiry实现独自计算。

注意事项:

  • expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。
  • 缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)。
public class CaffeineEvictionPolicyBaseOnTimeTest {	/**
	 * 从数据库获取数据
	 * @param key
	 * @return
	 */private String getFromDataBase(int key) {		return key + RandomStringUtils.randomAlphabetic(8);
	}	/**
	 * 到期策略
	 * @throws InterruptedException
	 */@Testpublic void testExpirePolicy() throws InterruptedException {		// 基于固定的到期策略进行回收
		LoadingCache<Integer, Object> cache = Caffeine.newBuilder()				// TODO 在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。
				.expireAfterAccess(5, TimeUnit.MINUTES)
				.build(key -> getFromDataBase(key));
		LoadingCache<Integer, Object> cache1 = Caffeine.newBuilder()				// TODO 在最后一次写入缓存后开始计时,在指定的时间后过期。
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.build(key -> getFromDataBase(key));
	}	/**
	 * 自定义到期策略
	 */@Testpublic void testCustomerExpirePolicy() {
		LoadingCache<Integer, Object> cache2 = Caffeine.newBuilder()
				.expireAfter(new Expiry<Object, Object>() {					@Overridepublic long expireAfterCreate(@NonNull Object key, @NonNull Object value, long currentTime) {						return 0;
					}					@Overridepublic long expireAfterUpdate(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {						return 0;
					}					@Overridepublic long expireAfterRead(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {						return 0;
				}).build(key -> getFromDataBase(key));
}复制代码

3、基于引用回收:

  • weakKeys(): 使用弱引用存储key。如果没有其他地方对该key有强引用,那么该缓存就会被垃圾回收器回收。由于垃圾回收器只依赖于身份(identity)相等,因此这会导致整个缓存使用身份 (==) 相等来比较 key,而不是使用 equals()。
  • weakValues() :使用弱引用存储value。如果没有其他地方对该value有强引用,那么该缓存就会被垃圾回收器回收。由于垃圾回收器只依赖于身份(identity)相等,因此这会导致整个缓存使用身份 (==) 相等来比较 key,而不是使用 equals()。
  • softValues() :使用软引用存储value。当内存满了过后,软引用的对象以将使用最近最少使用(least-recently-used ) 的方式进行垃圾回收。由于使用软引用是需要等到内存满了才进行回收,所以我们通常建议给缓存配置一个使用内存的最大值。 softValues() 将使用身份相等(identity) (==) 而不是equals() 来比较值。

注意事项:

  • AsyncLoadingCache不支持弱引用和软引用
  • Caffeine.weakValues()和Caffeine.softValues()不可以一起使用
  • 使用到的回收策略时LRU算法
public class CaffeineEvictionPolicyBaseOnReferenceTest {	/**
	 * 从数据库获取数据
	 * @param key
	 * @return
	 */private String getFromDataBase(int key) {		return key + RandomStringUtils.randomAlphabetic(8);
	}	@Testpublic void test() {
		LoadingCache<Integer, String> cache1 = Caffeine.newBuilder()				// 当key和value都没有引用时(都为null)驱逐缓存
				.weakKeys()
				.weakValues()
				.build(key -> getFromDataBase(key));
		LoadingCache<Integer, String> cache2 = Caffeine.newBuilder()				// 当垃圾收集器需要释放内存时驱逐
				.softValues()
				.build(key -> getFromDataBase(key));
}复制代码

五、Caffeine的移除监听器

如果我们需要在缓存被移除的时候,得到通知产生回调,并做一些额外处理工作。这个时候RemovalListener就派上用场了。

删除侦听器的里面的操作是使用Executor来异步执行的。默认执行程序是ForkJoinPool.commonPool(),可以通过Caffeine.executor(Executor)覆盖。

public class CaffeineRemovalTest {	@Testpublic void test() throws InterruptedException {
		Cache<Integer, String> cache = Caffeine.newBuilder()				// TODO 指定最大缓存数量,如果超过这个值,则会剔除很久没有被访问过或者不经常使用的缓存
				.maximumSize(2)
				.removalListener(new RemovalListener<Object, Object>() {					@Overridepublic void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
						System.out.println("【日志打印】触发了 " + cause.name() + " 策略, 清除的key = " + key + ", value=" + value);
				.build();
		cache.put(1, "a");
		System.out.println(cache.estimatedSize());
		cache.put(2, "b");
		System.out.println(cache.estimatedSize());
		System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2)));		// 触发缓存回收(异步)
		cache.put(3, "c");
		System.out.println(cache.estimatedSize());
		System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2, 3)));		// 触发缓存回收(异步)
		cache.put(4, "d");
		System.out.println(cache.estimatedSize());		// 缓存回收是异步执行,cleanUp()可以等待异步执行完成// cache.cleanUp();
		Thread.sleep(1000);
		System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2, 3, 4)));
		cache.put(5, "e");		// 手动删除缓存
		cache.invalidate(5);
		System.out.println(cache.getAllPresent(Lists.newArrayList(1, 2, 3, 4)));
}复制代码

运行结果:

1
{1=a, 2=b}
{1=a, 2=b, 3=c}
【日志打印】触发了 SIZE 策略, 清除的key = 1, value=a
【日志打印】触发了 SIZE 策略, 清除的key = 3, value=c
{2=b, 4=d}
【日志打印】触发了 EXPLICIT 策略, 清除的key = 5, value=e
【日志打印】触发了 SIZE 策略, 清除的key = 4, value=d
{2=b}复制代码

六、Caffeine的CacheWriter

CacheWriter允许缓存充当一个底层资源的代理,当与CacheLoader结合使用时,所有对缓存的读写操作都可以通过Writer进行传递。Writer可以把操作缓存和操作外部资源扩展成一个同步的原子性操作。并且在缓存写入完成之前,它将会阻塞后续的更新缓存操作,但是读取(get)将直接返回原有的值。 如果写入程序失败,那么原有的key和value的映射将保持不变,如果出现异常将直接抛给调用者。

能监听到的动作:

  • CacheWriter可以同步的监听到缓存的创建、变更和删除操作。

不能监听到的动作:

  • 加载(例如,LoadingCache.get)、重新加载(例如,LoadingCache.refresh)和计算(例如Map.computeIfPresent)的操作不被CacheWriter监听到。

注意事项:

  • CacheWriter不能与弱键或AsyncLoadingCache一起使用。

应用场景:

  • 可以配合外部存储使用
public class CaffeineWriterTest {	/**
	 * 充当二级缓存用,生命周期仅活到下个gc
	 */private Map<Integer, WeakReference<Integer>> secondCacheMap = new ConcurrentHashMap<>();	@Testpublic void test() throws InterruptedException {
		LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
				.maximumSize(1)
				.writer(						new CacheWriter<Integer, Integer>() {							/**
							 * 写入到外部存储
							 * @param key
							 * @param value
							 */@Overridepublic void write(@NonNull Integer key, @NonNull Integer value) {
								secondCacheMap.put(key, new WeakReference<>(value));
								System.out.println("写入到外部存储,将key = " + key + "放入二级缓存中");
								System.out.println("二级缓存: " + secondCacheMap);
								System.out.println();
							}							/**
							 * 删除外部存储
							 * @param key
							 * @param value
							 * @param cause
							 */@Overridepublic void delete(@NonNull Integer key, @Nullable Integer value, @NonNull RemovalCause cause) {								switch (cause) {									// 手动调用invalidate或remove等方法case EXPLICIT:
										secondCacheMap.remove(key);
										System.out.println("删除外部存储, " + cause.name() + " 策略, 将key = " + key);
										System.out.println("二级缓存: " + secondCacheMap);
										System.out.println();										break;									// 超出缓存设定大小case SIZE:
										secondCacheMap.remove(key);
										System.out.println("删除外部存储, " + cause.name() + " 策略, 将key = " + key);
										System.out.println("二级缓存: " + secondCacheMap);
										System.out.println();										break;									// 调用put等方法进行修改case REPLACED:
										secondCacheMap.remove(key);
										System.out.println("删除外部存储, " + cause.name() + " 策略, 将key = " + key);
										System.out.println("二级缓存: " + secondCacheMap);
										System.out.println();										break;									// 设置了key或value的引用方式case COLLECTED:
										secondCacheMap.remove(key);
										System.out.println("删除外部存储, " + cause.name() + " 策略, 将key = " + key);
										System.out.println("二级缓存: " + secondCacheMap);
										System.out.println();										break;									// 设置了过期时间case EXPIRED:
										secondCacheMap.remove(key);
										System.out.println("删除外部存储, " + cause.name() + " 策略, 将key = " + key);
										System.out.println("二级缓存: " + secondCacheMap);
										System.out.println();										break;									default:										break;
				.removalListener(new RemovalListener<Object, Object>() {					@Overridepublic void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
						System.out.println("【日志打印】触发了 " + cause.name() + " 策略, 清除的key = " + key + ", value=" + value);
				.build(new CacheLoader<Integer, Integer>() {						   /**
							* 默认情况下,get、getAll将会对缓存中没有值的key分别调用CacheLoader.load方法来构建缓存的值。
							* @param key
							* @return
						   @Nullable
						   @Override
						   public Integer load(@NonNull Integer key) {
							   WeakReference<Integer> value = secondCacheMap.get(key);							   if (value == null) {								   return 100;
							   System.out.println("触发CacheLoader#load(),从二级缓存读取key = " + key);
							   System.out.println();							   return value.get();
		cache.put(1, 1);
		cache.put(2, 2);		// 缓存回收是异步执行,cleanUp()可以等待异步执行完成// cache.cleanUp();
		Thread.sleep(1000);		// 根据key查询一个缓存,如果没有将返回提供默认值,并保存到缓存,又超过了缓存最大值,触发清理操作
		System.out.println(cache.get(1));
		Thread.sleep(1000);
		System.out.println(cache.getIfPresent(2));
}复制代码

运行结果:

写入到外部存储,将key = 1放入二级缓存中
二级缓存: {1=java.lang.ref.WeakReference@47d384ee}
写入到外部存储,将key = 2放入二级缓存中
二级缓存: {1=java.lang.ref.WeakReference@47d384ee, 2=java.lang.ref.WeakReference@1ff8b8f}
删除外部存储, SIZE 策略, 将key = 1
二级缓存: {2=java.lang.ref.WeakReference@1ff8b8f}
【日志打印】触发了 SIZE 策略, 清除的key = 1, value=1
删除外部存储, SIZE 策略, 将key = 2
二级缓存: {}
【日志打印】触发了 SIZE 策略, 清除的key = 2, value=2
null复制代码

六、Caffeine的统计

public class CaffeineStatsTest {	@Testpublic void test() {
		Cache<Integer, String> cache = Caffeine.newBuilder()
				.maximumSize(10_00)
				.recordStats()
				.build();		for (int i = 0; i < 100; i++) {
			cache.get(i, new Function<Integer, String>() {				@Overridepublic String apply(Integer integer) {					return  RandomStringUtils.randomAlphanumeric(8);
		}		for (int i = 0; i < 100; i++) {			if (i % 2 == 0) {
				System.out.println(i);
				System.out.println(cache.getIfPresent(i));
				System.out.println();
		System.out.println("加载新值所花费的平均时间 totalLoadTime / (loadSuccessCount + loadFailureCount): " + cache.stats().averageLoadPenalty());
		System.out.println("缓存逐出的数量: " + cache.stats().evictionCount());
		System.out.println("缓存逐出的数量(权重): " + cache.stats().evictionWeight());
		System.out.println("返回命中缓存的总数: " + cache.stats().hitCount());
		System.out.println("返回命中与请求的比率: " + cache.stats().hitRate());
		System.out.println("加载新值的总次数: " + cache.stats().loadCount());
		System.out.println("加载新值成功的总次数: " + cache.stats().loadSuccessCount());
		System.out.println("加载新值失败的总次数: " + cache.stats().loadFailureCount());
		System.out.println("加载新值失败的比率: " + cache.stats().loadFailureRate());
		System.out.println("missCount: " + cache.stats().missCount());
		System.out.println("missRate: " + cache.stats().missRate());
		System.out.println("hitCount + missCount: " + cache.stats().requestCount());
		System.out.println("加载新值所花费的总纳秒数: " + cache.stats().totalLoadTime());
}复制代码

运行结果:

加载新值所花费的平均时间 totalLoadTime / (loadSuccessCount + loadFailureCount): 23188.66
缓存逐出的数量: 0
缓存逐出的数量(权重): 0
返回命中缓存的总数: 50
返回命中与请求的比率: 0.3333333333333333
加载新值的总次数: 100
加载新值成功的总次数: 100
加载新值失败的总次数: 0
加载新值失败的比率: 0.0
missCount: 100
missRate: 0.6666666666666666
hitCount + missCount: 150
加载新值所花费的总纳秒数: 2318866复制代码

七、Caffeine的刷新

刷新并不是到期就刷新,而是对这个数据再次访问之后,才会刷新。只阻塞加载数据的线程,其余线程返回旧数据。

public class CaffeineRefreshTest {	@Testpublic void test() throws InterruptedException {
		System.out.println("开始时间: " + LocalDateTime.now());
		System.out.println();
		LoadingCache<Integer, String> cache = Caffeine.newBuilder()
				.expireAfterWrite(Duration.ofSeconds(5))				// TODO 注意这里的刷新并不是到期就刷新,而是对这个数据再次访问之后,才会刷新。只阻塞加载数据的线程,其余线程返回旧数据。
				.refreshAfterWrite(Duration.ofSeconds(2))
				.build(new CacheLoader<Integer, String>() {						   /**
							* 刷新数据逻辑,调用此方法的线程,获取的是旧数据
							* @param key
							* @return
							* @throws Exception
						   @Nullable
						   @Override
						   public String load(@NonNull Integer key) throws Exception {
							   System.out.println("刷新时间: " + LocalDateTime.now());
							   System.out.println("2当前所在线程:" + Thread.currentThread().getName());							   int i = RandomUtils.nextInt(0, 20);
							   System.out.println(i);
							   System.out.println();							   if (i % 2 == 0) {								   return "wxyz";
							   } else {								   return "WXYZ";
		cache.put(1, "abcd");
		String value1 = cache.get(1);
		System.out.println("第1次访问key=1:" + value1);
		System.out.println("1当前所在线程:" + Thread.currentThread().getName());
		System.out.println();		// 触发刷新后,第一次返回旧数据
		Thread.sleep(3000);
		System.out.println("3s后访问key=1:" + cache.get(1));
		System.out.println();		// 返回新数据
		String value2 = cache.get(1);
		String result2 = value2.equals(value1) ? "false" : "true";
		System.out.println("第3次获取key=1:" + value2 + ",是否刷新值=" + result2);
		System.out.println();		// 触发刷新后,第一次返回旧数据
		Thread.sleep(3000);
		String value3 = cache.get(1);
		String result3 = value3.equals(value2) ? "false" : "true";
		System.out.println("6s次后访问key=1:" + value3 + ",是否刷新值=" + result3);
		Thread.sleep(100);		// 返回新数据
		String value4 = cache.get(1);
		String result4 = value4.equals(value3) ? "false" : "true";
		System.out.println("第4次获取key=1:" + value4 + ",是否刷新值=" + result4);
}复制代码

运行结果:

开始时间: 2020-12-31T17:42:02.721
第1次访问key=1:abcd
1当前所在线程:main
刷新时间: 2020-12-31T17:42:05.784
2当前所在线程:ForkJoinPool.commonPool-worker-1
3s后访问key=1:abcd
第3次获取key=1:WXYZ,是否刷新值=true
6s次后访问key=1:WXYZ,是否刷新值=false
刷新时间: 2020-12-31T17:42:08.799
2当前所在线程:ForkJoinPool.commonPool-worker-1
第4次获取key=1:wxyz,是否刷新值=true复制代码

八、总结:

1、掌握Caffeine的3种填充策略:

  • 手动填充数据
  • 同步加载
  • 异步加载

2、掌握Caffeine提供了3种回收策略:

  • 基于大小回收
  • 基于时间回收
  • 基于引用回收

3、Caffeine的移除监听器

如果我们需要在缓存被移除的时候,得到通知产生回调,并做一些额外处理工作。这个时候RemovalListener就派上用场了。

删除侦听器的里面的操作是使用Executor来异步执行的。默认执行程序是ForkJoinPool.commonPool(),可以通过Caffeine.executor(Executor)覆盖。

4、Caffeine的CacheWriter

CacheWriter允许缓存充当一个底层资源的代理,当与CacheLoader结合使用时,所有对缓存的读写操作都可以通过Writer进行传递。Writer可以把操作缓存和操作外部资源扩展成一个同步的原子性操作。并且在缓存写入完成之前,它将会阻塞后续的更新缓存操作,但是读取(get)将直接返回原有的值。 如果写入程序失败,那么原有的key和value的映射将保持不变,如果出现异常将直接抛给调用者。

能监听到的动作:

  • CacheWriter可以同步的监听到缓存的创建、变更和删除操作。

不能监听到的动作:

  • 加载(例如,LoadingCache.get)、重新加载(例如,LoadingCache.refresh)和计算(例如Map.computeIfPresent)的操作不被CacheWriter监听到。

注意事项:

  • CacheWriter不能与弱键或AsyncLoadingCache一起使用。

应用场景:

  • 可以配合外部存储使用

5、Caffeine的统计

6、Caffeine的刷新

  • 刷新并不是到期就刷新,而是对这个数据再次访问之后,才会刷新。只阻塞加载数据的线程,其余线程返回旧数据。
高性能内存对象缓存——Memcached

Memcached 是一套开源的高性能分布式内存对象缓存系统,它将所有的数据都存储在内存中,因为在内存中会统一维护一张巨大的 Hash 表,所以支持任意存储类型的数据。很多网站通过使用 Memcached 提高网站的访问速度,尤其是对于大型的需要频繁访问数据的网站。Memcached 是典型的 C/S 架构,因此需要安装 Memcached 服务端与 Memcached API 客户端。Memcached 服务端是用 C 语音编写的,而 Memcached API 客户端可以用任何语言来编写,如PHP、Python、Perl 等,并通过 Memcached 协议与 Memcached 服务端进行通信。

c# 调用mongodb 自定义函数 .net mongodb

需要引用MongoDB.Driver.dll、MongoDB.Driver.core.dll、MongoDB.Bson.dll三个dll。1、数据库连接:public class MongoDb //方式一 //private static readonly string connStr = "mongodb://myAdmin:123456@local