一、为什么要用本地缓存?
正常情况下,我们不使用本地缓存而直接使用分布式缓存(如Redis)的话,就会涉及网络调用,会出现一定的性能消耗,所以在大型项目中,一般使用多级缓存也就是本地缓存+分布式缓存来实现缓存查询,进一步提高系统性能。(具体可参考《如何实现多级缓存?》)
二、实现本地缓存要考虑什么问题?
想要实现本地缓存,要有以下几个考虑点:
- 数据结构:为了提高查询性能,我们一般可以用Key-Value数据结构来进行数据存储(如HashMap)
- 线程安全:一般情况下,本地缓存的使用场景和使用频率会比较多,所以要考虑到线程安全问题
- 清除策略:为了防止创建了大量对象而导致OOM的问题,还需要加上清除策略进行定期清除(如LRU算法、LFU算法等)
- 过期时间:为了防止某些数据一直没有被清除到,我们还需要加上过期时间,保证对应Key一定会被清除回收
三、如何实现本地缓存?
一般最简单的方式就是直接使用HashMap
来实现,因为它本身就是一个Key-Value结构,如果要考虑线程安全问题,也可以使用ConcurrentHashMap
。但不足的是需要自己加上清除策略和过期时间,防止内存占用过大。
当前市面上已经有了成熟的本地缓存框架可以直接使用,比如:
- Guava Cache
- Caffeine
1、Guava Cache
添加依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
使用示例:
public class TestGuavaCacheClass {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
// 设置缓存最大容量为100(超过100会使用LRU算法清除)
.maximumSize(100)
// 设置缓存初始容量为10,默认为16
.initialCapacity(16)
// 设置并发级别(即可以同时写缓存的线程数,默认为4)
.concurrencyLevel(8)
// 设置写缓存后n秒后过期
.expireAfterWrite(30, TimeUnit.SECONDS)
// 设置读写缓存后n秒后过期(跟expireAfterWrite类似)
.expireAfterAccess(30, TimeUnit.SECONDS)
// 设置写缓存后n秒后刷新,只会阻塞第一个访问的线程,其他线程访问返回旧值
.refreshAfterWrite(30, TimeUnit.SECONDS)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return loadCache(key);
}
});
public String getCache(String key) throws Exception {
return cache.get(key);
}
private String loadCache(String key) {
return "xxx";
}
}
2、Caffeine
//TODO