前言
对一个java开发者而言,提到缓存,第一反应就是Redis。利用这类缓存足以解决大多数的性能问题了,我们也要知道,这种属于remote cache(分布式缓存),应用的进程和缓存的进程通常分布在不同的服务器上,不同进程之间通过RPC或HTTP的方式通信。这种缓存的优点是缓存和应用服务解耦,支持大数据量的存储,缺点是数据要经过网络传输,性能上会有一定损耗。
与分布式缓存对应的是本地缓存,缓存的进程和应用进程是同一个,数据的读写都在一个进程内完成,这种方式的优点是没有网络开销,访问速度很快。缺点是受JVM内存的限制,不适合存放大数据。
本地缓存常用技术
本地缓存和应用同属于一个进程,使用不当会影响服务稳定性,所以通常需要考虑更多的因素,例如容量限制、过期策略、淘汰策略、自动刷新等。常用的本地缓存方案有:
- Guava Cache
- Caffeine
- EhCache
基于Guava Cache实现本地缓存
Guava是Google团队开源的一款 Java 核心增强库,包含集合、并发原语、缓存、IO、反射等工具箱,性能和稳定性上都有保障,应用十分广泛。Guava Cache支持很多特性:
- 支持最大容量限制
- 支持两种过期删除策略(插入时间和访问时间)
- 支持简单的统计功能
- 基于LRU算法实现
引入依赖
代码语言:javascript复制<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
简单的示例:
代码语言:javascript复制public class GuavaCacheTest {
public static void main(String[] args) throws Exception {
//创建guava cache
Cache<String, String> loadingCache = CacheBuilder.newBuilder()
//cache的初始容量
.initialCapacity(5)
//cache最大缓存数
.maximumSize(10)
//设置写缓存后n秒钟过期
.expireAfterWrite(17, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
.build();
String key = "key";
// 往缓存写数据
loadingCache.put(key, "v");
// 获取value的值,如果key不存在,调用collable方法获取value值加载到key中再返回
String value = loadingCache.get(key, new Callable<String>() {
@Override
public String call() throws Exception {
return getValueFromDB(key);
}
});
// 删除key
loadingCache.invalidate(key);
}
private static String getValueFromDB(String key) {
return "v";
}
}
Caffeine
Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 Spring5 (Springboot 2.x) 后,Spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件。
代码语言:javascript复制<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.5.5</version>
</dependency>
使用Caffeine:
代码语言:javascript复制public class CaffeineCacheTest {
public static void main(String[] args) throws Exception {
//创建guava cache
Cache<String, String> loadingCache = Caffeine.newBuilder()
//cache的初始容量
.initialCapacity(5)
//cache最大缓存数
.maximumSize(10)
//设置写缓存后n秒钟过期
.expireAfterWrite(17, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
.build();
String key = "key";
// 往缓存写数据
loadingCache.put(key, "v");
// 获取value的值,如果key不存在,获取value后再返回
String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);
// 删除key
loadingCache.invalidate(key);
}
private static String getValueFromDB(String key) {
return "v";
}
}
相比Guava Cache来说,Caffeine无论从功能上和性能上都有明显优势。同时两者的API类似,使用Guava Cache的代码很容易可以切换到Caffeine,节省迁移成本。
EhCache
EhCache是一个纯Java的进程内缓存框架,具有快速、精干的特点。注意的这里的关键字进程,基于进程的缓存直觉告诉我们效率肯定要高一些,因为它直接在进程之内进行操作,但不同应用之间缓存的共享可能就会有问题。
EhCache是Hibernate中默认的CacheProvider,Spring Boot也对其进行了支持,Spring中提供的缓存抽象也支持对EhCache缓存框架的绑定,而且支持基于注解的方式来使用。因此,EhCache是一款被广泛使用的基于Java的高速缓存框架,使用起来也非常方便。
EhCache提供了多种缓存策略,主要分为内存和磁盘两级,是一款面向通用缓存、Java EE和轻量级容器的缓存框架。
代码语言:javascript复制<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.0</version>
</dependency>
使用EhCache
代码语言:javascript复制public class EncacheTest {
public static void main(String[] args) throws Exception {
// 声明一个cacheBuilder
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("encacheInstance", CacheConfigurationBuilder
//声明一个容量为20的堆内缓存
.newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))
.build(true);
// 获取Cache实例
Cache<String,String> myCache = cacheManager.getCache("encacheInstance", String.class, String.class);
// 写缓存
myCache.put("key","v");
// 读缓存
String value = myCache.get("key");
// 移除换粗
cacheManager.removeCache("myCache");
cacheManager.close();
}
}
总结
- 从易用性角度,Guava Cache、Caffeine和EhCache都有十分成熟的接入方案,使用简单。
- 从功能性角度,Guava Cache和Caffeine功能类似,都是只支持堆内缓存,Encache相比功能更为丰富
- 从性能上进行比较,Caffeine最优、GuavaCache次之,EhCache最差(下图是三者的性能对比结果)
总体来说,对于本地缓存的方案中,个人比较推荐Caffeine,性能上遥遥领先。真实的业务工程中,建议使用Caffeine作为本地缓存,另外使用Redis或者memcache作为分布式缓存,构造多级缓存体系,保证性能和可靠性。