准备工作
创建缓存
使用缓存
Cache 读取缓存
LoadingCache 读取缓存
修改缓存
Cache 修改缓存
LoadingCache 修改缓存
其他方法
在前面的文章 Spring Cache 使用教程:注解方式和API接口方式 中讲到了在程序开发中,在一些需要复杂或耗时的IO或CPU操作中,我们经常会使用缓存,将操作的结果保存起来,方便下次直接从缓存中读取结果。在上篇文章中,我们用的是Spring Cache,使用Redis作为缓存服务器。然而有些情况下,比如
- 程序使用单机模式部署(只有一台服务器)
- 数据只用来读取,不会修改
对于这类情况,使用Redis缓存效率较低(需要进行网络传输),这个时候就可以考虑使用本地缓存了。本地缓存顾名思义,就是运行在应用程序中的缓存,直接在程序内存中,读写效率更高。
在Java中,guava 为我们提供了一个很好的本地缓存 guava cache。(而且guava也包含了很多实用工具类。)所以这篇文章主要介绍如何使用 guava 缓存。
guava 提供了两种缓存
Cache
必须手动存入缓存数据,相当于一个加强版的MapLoadingCache
可以自动加载数据的缓存,继承了 Cache
下面结合具体的代码介绍上面两种缓存的使用
准备工作
- 添加依赖
com.google.guavaguava27.1-jre
- 需要缓存的数据类
private static class Data {
private Integer id;
private String value;
// gatter and setter
}
创建缓存
详细介绍请参看代码和注释
/**
* 普通的缓存 Cache
*/
private Cache dataCache = CacheBuilder.newBuilder() // 使用 CacheBuilder 创建
.expireAfterAccess(Duration.ofMinutes(30)) // 设置过期时间,在每次访问的半小时后过期,(再次访问则重新等待半小时)// .expireAfterWrite(Duration.ofHour(2)) 在每次写入缓存2小时后过期
.maximumSize(10240) // 缓存最大数量
.concurrencyLevel(4) // 指定并发修改的数量,默认4
.initialCapacity(2048) // 设置初始化大小,避免经常扩容
.build(); // 无参的 build 方法创建 Cache/**
* 可以自动加载数据的缓存 LoadingCache
*/private LoadingCache dataLoadingCache = CacheBuilder.newBuilder()
.expireAfterAccess(Duration.ofMinutes(30)) // 设置访问后30min的过期时间
.initialCapacity(1024)
.maximumSize(10240)
.refreshAfterWrite(Duration.ofHours(2)) // 在写入缓存后的2小时后,自动刷新缓存
.build(new CacheLoader() { // build(CacheLoader) 创建 LoadingCache 缓存@Override // 加载缓存的方法, 必须实现public Data load(Integer key) throws Exception {return getDataById(key); // 从数据库查询数据
}@Override // 重写批量加载缓存的方法, 不批量查询缓存可以不重写,批量查询必须重写, 父类方法会直接抛异常public Map loadAll(Iterable extends Integer> keys) throws Exception {
List ids = Lists.newArrayList(keys.iterator()); // Lists 是 guava 提供的工具类,贼好用
List dataList = listDataByIds(ids); // 从数据库批量查询数据return dataList.stream().collect(Collectors.toMap(Data::getId, Function.identity())); // 必须要转成Map,因为loadIngCache 会把这些再存入缓存中
}
});
使用缓存
Cache 读取缓存
V getIfPresent(Object key);
如果存在则获取缓存数据,不存在返回null,缓存数据为null也返回nullV get(K key, Callable extends V> loader) throws ExecutionException;
获取缓存,缓存不存在则通过 loader 获取数据,并将数据缓存起来ImmutableMap getAllPresent(Iterable> keys);
批量获取存在的缓存数据,ImmutableMap 为不可修改的Map(不能对其执行修改操作,否则抛异常)
// 一般都用第二种方法
// 但其实这个方法也有点不合理的地方,使用 Callable 接口获取数据,这个接口的方法是会抛异常的,然后被转化成了 ExecutionException 重新抛出来,还得手动处理一下
// 个人觉得用 Function 这种接口更好,获取参数,然后返回数据,有异常自己处理
private Data getDataByIdFromCache(Integer id) {
try {
return dataCache.get(id, () -> getDataById(id)); // 获取缓存
} catch (ExecutionException e) {
log.error("获取缓存异常", e); // 记录错误日志
// e.printStackTrace(); 本地测试直接答应你就可以
}
// 执行异常,直接从数据库里查
return getDataById(id);
}
LoadingCache 读取缓存
由于 LoadingCache 继承了 Cache,所以上面的方法都有,此外还有以下获取缓存的方法
V get(K key) throws ExecutionException;
获取缓存,不存在调用创建时传入的 load 方法加载缓存数据V getUnchecked(K key);
同上,不抛出ExecutionException异常ImmutableMap getAll(Iterable extends K> keys) throws ExecutionException;
批量查询缓存数据
// 批量查询缓存数据
private List listDataFromCache(List ids) {
try {
ImmutableMap allData = dataLoadingCache.getAll(ids);return new ArrayList<>(allData.values());
} catch (ExecutionException e) {
log.error("批量获取缓存异常", e);
}return listDataByIds(ids);
}
修改缓存
Cache 修改缓存
void put(K key, V value);
存入缓存数据void putAll(Map extends K, ? extends V> m);
批量放入缓存void invalidate(Object key);
使缓存 key 对应的数据过期void invalidateAll(Iterable> keys);
批量过期void invalidateAll();
全部缓存数据过期
LoadingCache 修改缓存
除上面 Cache 方法外,LoadingCache 还有
void refresh(K key);
刷新缓存
使用示例
// 删除数据时,将对应的缓存也清掉
private void deleteById(Integer id) {
// delete from db
dataCache.invalidate(id);
}
// 修改数据时,需要刷新对应的缓存
private void updateData(Data data) {
// update data into db
// LoadingCache 可以刷新缓存
dataLoadingCache.refresh(data.getId());
// Cache 手动存入新的
dataCache.put(data.getId(), data);
}
// 添加新数据时,Cache 手动存入, 也可以等查的时候再存
private void addData(Data data) {
// Cache 需要手动存入
dataCache.put(data.getId(), data);
}
其他方法
long size();
缓存数据的数量CacheStats stats();
获取缓存的统计对象,主要统计缓存命中数量,命中率,报错率,加载数据的时间等ConcurrentMap asMap();
将缓存转化成 ConcurrentMap,对map的修改会影响到实际的缓存
以上就这本篇文章关于 Guava 缓存的使用介绍了。如果有上面不懂的或者有想了解的后端知识点,欢迎私信我。
下篇文章会介绍如何使对 Spring Cache (Redis实现)进行封装, 使得能够对缓存进行批量操作。
关注我的 CSDN 账号 查看更多博客