Guava cache本地缓存简介、刷新机制简单示例及封装

简介

Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

通常来说,Guava Cache适用于:

  • 你愿意消耗一些内存空间来提升速度。
  • 你预料到某些键会被查询一次以上。
  • 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)

如果你的场景符合上述的每一条,Guava Cache就适合你。

如同范例代码展示的一样,Cache实例通过CacheBuilder生成器模式获取,但是自定义你的缓存才是最有趣的部分。

:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。

Guava Cache的使用示例

使用缓存时,最常遇到的场景需要就是:

"获取缓存-如果没有-则计算"[get-if-absent-compute]的原子语义.

具体含义:

  1. 从缓存中取。
  2. 缓存中存在该数据,直接返回;
  3. 缓存中不存在该数据,从数据源中取。
  4. 数据源中存在该数据,放入缓存,并返回;
  5. 数据源中不存在该数据,返回空。

刷新机制

三种基于时间的清理或刷新缓存数据的方式:

expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。

expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。

refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。

一、定时过期

LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
                .maximumSize(100)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return generateValueByKey(key);
                    }
                });
try {
    System.out.println(caches.get("key-zorro"));
} catch (ExecutionException e) {
    e.printStackTrace();
}

如代码所示新建了名为caches的一个缓存对象,定义了缓存大小、过期时间及缓存值生成方法。maximumSize定义了缓存的容量大小,当缓存数量即将到达容量上线时,则会进行缓存回收,回收最近没有使用或总体上很少使用的缓存项。需要注意的是在接近这个容量上限时就会发生,所以在定义这个值的时候需要视情况适量地增大一点。 
另外通过expireAfterWrite这个方法定义了缓存的过期时间,写入十分钟之后过期。 
在build方法里,传入了一个CacheLoader对象,重写了其中的load方法。当获取的缓存值不存在或已过期时,则会调用此load方法,进行缓存值的计算。 

二、定时刷新

LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
                .maximumSize(100)
                .refreshAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return generateValueByKey(key);
                    }
                });
try {
    System.out.println(caches.get("key-zorro"));
} catch (ExecutionException e) {
    e.printStackTrace();
}

每隔十分钟缓存值则会被刷新。

有一个需要注意也很难处理的地方,这里的定时并不是真正意义上的定时。Guava cache的刷新需要依靠用户请求线程,让该线程去进行load方法的调用,所以如果一直没有用户尝试获取该缓存值,则该缓存也并不会刷新。

三、异步刷新

ListeningExecutorService backgroundRefreshPools = 
                MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));
        LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
                .maximumSize(100)
                .refreshAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return generateValueByKey(key);
                    }
 
                    @Override
                    public ListenableFuture<Object> reload(String key,
                            Object oldValue) throws Exception {
                        return backgroundRefreshPools.submit(new Callable<Object>() {
 
                            @Override
                            public Object call() throws Exception {
                                return generateValueByKey(key);
                            }
                        });
                    }
                });
try {
    System.out.println(caches.get("key-zorro"));
} catch (ExecutionException e) {
    e.printStackTrace();
}

当缓存的key很多时,高并发条件下大量线程同时获取不同key对应的缓存,此时依然会造成大量线程阻塞,并且给数据库带来很大压力。这个问题的解决办法就是将刷新缓存值的任务交给后台线程,所有的用户请求线程均返回旧的缓存值,这样就不会有用户线程被阻塞了。

{

  • 可以看到防缓存穿透和防用户线程阻塞都是依靠返回旧值来完成的。所以如果没有旧值,同样会全部阻塞,因此应视情况尽量在系统启动时将缓存内容加载到内存中。

  • 在刷新缓存时,如果generateValueByKey方法出现异常或者返回了null,此时旧值不会更新。

  • 题外话:在使用内存缓存时,切记拿到缓存值之后不要在业务代码中对缓存直接做修改,因为此时拿到的对象引用是指向缓存真正的内容的。如果需要直接在该对象上进行修改,则在获取到缓存值后拷贝一份副本,然后传递该副本,进行修改操作

}

封装

封装后的的缓存抽象类实现了刷新时间、时间单位、定时刷新及初始化缓存值等方法。实现类只需要设置自己的缓存时间等信息,实现preload()方法加载缓存数据,实现getCacheValue()方法将缓存数据添加到缓存中。

抽象类代码如下:

package com.lenchy.lms.util.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.homolo.datamodel.manager.EntityManager;
import com.homolo.framework.setup.Initializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;

import javax.validation.constraints.NotNull;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 数据缓存抽象类
 *
 * @param <T> 缓存值的类型
 */
public abstract class BaseDataCache<T> extends Initializer {
	private static final Logger LOGGER = LoggerFactory.getLogger(BaseDataCache.class);
	private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
	@Autowired
	private TaskExecutor taskExecutor;
	@Autowired
	protected EntityManager entityManager;

	private LoadingCache<String, T> cache;

	/**
	 * @return 时间
	 */
	protected abstract long duration();

	/**
	 * @return 时间单位
	 */
	protected abstract TimeUnit unit();

	/**
	 * 根据key生成缓存值的方法
	 *
	 * @param key key
	 * @return 缓存值
	 */
	protected abstract T getCacheValue(String key);

	/**
	 * 缓存到期是否自动刷新
	 */
	protected boolean refresh() {
		return false;
	}

	/**
	 * 预加载缓存数据,系统启动后执行
	 *
	 * @return Runnable 返回 null 表示不执行预加载
	 */
	protected Runnable preload() {
		return null;
	}

	/**
	 * 获取缓存值,请在初始化完成之后调用
	 *
	 * @param key key
	 * @return 缓存值,异常时返回 getCacheValue()
	 */
	public T get(String key) {
		try {
			return cache.get(key);
		} catch (ExecutionException e) {
			LOGGER.error("get cache error", e);
			return getCacheValue(key);
		}
	}

	@Override
	public void initialize() {
		long duration = duration();
		TimeUnit unit = unit();
		cache = CacheBuilder.newBuilder().refreshAfterWrite(duration(), unit())
				.build(new CacheLoader<String, T>() {
					@Override
					public T load(@NotNull String key) {
						return getCacheValue(key);
					}

					@Override
					public ListenableFuture<T> reload(final String key, T oldValue) throws Exception {
						ListenableFutureTask<T> task = ListenableFutureTask.create(new Callable<T>() {
							public T call() {
								return getCacheValue(key);
							}
						});
						taskExecutor.execute(task);
						return task;
					}
				});
		Runnable preload = preload();
		if (preload != null) {
			LOGGER.info("preload {}", this.getClass().getSimpleName());
			taskExecutor.execute(preload());
		}
		if (refresh()) {
			executorService.scheduleAtFixedRate(new Runnable() {
				@Override
				public void run() {
					for (String key : cache.asMap().keySet()) {
						cache.refresh(key);
					}
				}
			}, duration, duration, unit);
		}
	}

	@Override
	public int getPhase() {
		return 20000;
	}
}

简单实现类

@Component
public class Statistic4JusticeCache extends BaseDataCache<Statistic4JusticeCache.JusticeCache> {
	@Autowired
	private JusticeBureauManager justiceBureauManager;
	@Autowired
	protected NationalDataCountUtil nationalDataStatisticsUtil;
	@Autowired
	private JusticeBureauFilter justiceBureauFilter;

	@Override
	protected long duration() {
		return 1;
	}

	@Override
	protected TimeUnit unit() {
		return TimeUnit.DAYS;
	}

	@Override
	protected boolean refresh() {
		return true;
	}

	@Override
	protected Runnable preload() {
		return new Runnable() {
			@Override
			public void run() {
				List<String> cacheKwys = new ArrayList<>();
                cacheKwys.add( "first" ); 
                cacheKwys.add( "second" ); 
                cacheKwys.add( "third" ); 
				for (String key : cacheKwys) {
					get(key);
				}
			}
		};
	}

	@Override
	protected Statistic4JusticeCache.JusticeCache getCacheValue(String key) {
		JusticeCache cache = new JusticeCache();
		cache.setFirstList(managerGetList(key));
		cache.setSecondList(getZoneList(key));
		cache.setThirdMap(getLawyerModifymap(key));
		return cache;
	}

    public static class JusticeCache {
		private List firstList;
		private List secondList;
		private Map thirdMap;

		public List getFirstList() {
			return firstList;
		}

		public void setFirstList(List firstList) {
			this.firstList = firstList;
		}

		public List getSecondlist() {
			return secondList;
		}

		public void setSecondList(List secondList) {
			this.secondList = secondList;
		}

		public Map getThirdMap() {
			return thirdMap;
		}

		public void setThirdMap(Map thirdMap) {
			this.thirdMap = thirdMap;
		}
	}

    public List<String> getFirstList(){
        List first = new ArrayList<>();
        first.add("first1");
        first.add("first2");
        return first;
    }

    public List<String> getSecondlist(){
        List second = new ArrayList<>();
        first.add("second1");
        first.add("second2");
        return first;
    }

    public Map<String,String> getThirdMap(){
        Map<String, String> third = new HashMap<String, String>();
        third.put( "third1" , "third1" ); 
        third.put( "third2" , "third2" );
        return third; 
    }


}

有不足的地方希望大家指出。。。。。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Guava Cache是Google Guava库中提供的一种本地缓存解决方案。它是一个基于内存的缓存,可以在应用程序内部存储数据,提高应用程序性能。 Guava Cache提供了以下特性: 1. 自动加载:当缓存中不存在某个键的值时,可以自动加载生成该值。 2. 自动移除:缓存中的某些条目可以在一定时间内自动过期,或者可以使用大小限制来限制缓存中的条目数。 3. 针对不同的缓存数据设置不同的过期时间、存活时间、最大值、最小值等。 4. 支持同步和异步缓存。 使用Guava Cache非常简单,只需要按以下步骤操作: 1. 引入Guava库。 2. 创建一个CacheBuilder对象,用于配置缓存。 3. 调用build()方法创建一个Cache对象。 4. 使用put()方法向缓存中添加数据。 5. 使用get()方法从缓存中读取数据,如果缓存中不存在该键对应的值,则可以自动加载。 6. 使用invalidate()方法从缓存中移除数据。 下面是一个简单示例: ```java import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class GuavaCacheExample { public static void main(String[] args) throws ExecutionException { // 创建一个CacheBuilder对象 CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder() .maximumSize(100) // 设置缓存最大条目数 .expireAfterWrite(10, TimeUnit.MINUTES); // 设置缓存过期时间 // 创建一个Cache对象 LoadingCache<String, String> cache = cacheBuilder.build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("loading " + key); // 自动加载数据 return "value-" + key; } }); // 添加数据到缓存cache.put("key1", "value1"); cache.put("key2", "value2"); // 从缓存中读取数据 System.out.println(cache.get("key1")); // 输出"value1" System.out.println(cache.get("key3")); // 输出"loading key3"和"value-key3" // 移除缓存中的数据 cache.invalidate("key1"); System.out.println(cache.get("key1", () -> "default")); // 输出"default" } } ``` 在这个示例中,我们使用CacheBuilder对象配置了缓存的最大条目数和过期时间。我们还使用CacheLoader对象创建了一个自动加载的缓存,当缓存中不存在某个键的值时,可以自动加载生成该值。我们使用put()方法向缓存中添加了两个数据,使用get()方法从缓存中读取了两个数据,并使用invalidate()方法从缓存中移除了一个数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值