首页 > 代码库 > A comparison of local caches (1) 【本地缓存之比较 (1)】
A comparison of local caches (1) 【本地缓存之比较 (1)】
1. Spring local cache 【Spring 本地缓存】
Spring provided cacheable annotation since 3.1. It‘s very super convinient to use and can obviously boost application performance.
从3.1版本开始,Spring提供了cacheable注解。它使用起来非常方便,还可以很明显的提升应用性能。具体的,怎么使用呢?
First, create a cache bean.
首先,创建缓存bean。这里,我们设置一个三秒的本地缓存(写后3秒过期)。
@Bean
public Cache ephemeralCache() {
return new ConcurrentMapCache(EPHEMERAL_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build().asMap(), false);
}
Second, add @Cacheable to the existed method.
接下来,给想要使用缓存的已有方法加上@Cacheable注解
@Cacheable(cacheNames = AppCacheConfig.EPHEMERAL_CACHE, key = "{#root.methodName, ‘test‘}")
public Integer genId() {
int i = ai.incrementAndGet();
System.out.println(String.format("populate cache %s", LocalDateTime.now()));
return i;
}
Finally, enjoy!
然后,尽情体验缓存带来的快乐!
ExecutorService exe = Executors.newWorkStealingPool();
exe.submit(() -> {
while (true) {
cacheApi.genId();
Thread.sleep(50);
}
});
See output of below codes
下面的日志和最初的缓存设定完全吻合
2017-06-08 10:17:49.990 INFO 12460 --- [ main] com.loops.nbs.Application : Started Application in 7.42 seconds (JVM running for 7.973) populate cache 2017-06-08T10:17:52.372 populate cache 2017-06-08T10:17:55.379 populate cache 2017-06-08T10:17:58.387 populate cache 2017-06-08T10:18:01.394 populate cache 2017-06-08T10:18:04.402 populate cache 2017-06-08T10:18:07.409 populate cache 2017-06-08T10:18:10.417 populate cache 2017-06-08T10:18:13.426
2. Guava cache 【Guava 本地缓存】
Guava is a set of libraries provided by Google. In memory cache is part of it.
Guava是Google提供的一套工具库。这里只讨论其中的缓存部分。
public class GuavaCacheExample { static Cache<Integer, Integer> cache = CacheBuilder.newBuilder() .expireAfterWrite(2, TimeUnit.SECONDS) .recordStats() .build(); static LoadingCache<Integer, Integer> loadingCache = CacheBuilder.newBuilder() .expireAfterWrite(2, TimeUnit.SECONDS) .build(new CacheLoader<Integer, Integer>() { @Override public Integer load(Integer key) throws Exception { System.out.println("populate cache"); return key * 10; } }); static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); public static void main(String[] args) throws Exception { useNormalCache(); } static void useNormalCache() throws Exception { scheduler.scheduleWithFixedDelay(() -> System.out.println(cache.stats()), 0, 1, TimeUnit.SECONDS); for (int i = 0; i < 10; i++) { System.out.println(cache.get(0, () -> { Thread.sleep(500); return 10; })); Thread.sleep(300); } } static void useLoadingCache() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(loadingCache.get(1)); Thread.sleep(300); } } }
Usually, there are 2 types of caches
1) Cache
Cache is basic usage, it provides method to get and put cache.
Cache是基础用法,提供了get和put的操作。
2) LoadingCache
Loading cache requires a CacheLoader when cache is created. Every time when cache is expired or nonexisted, the load method will be called automatically and generate cache.
So user doesn‘t have to manually put cache back after cache is expired.
Loading cache在创建的时候需要给定 CacheLoader。如果缓存过期或者不存在,CacheLoader 的 load 方法就会被调用到并产生缓存。这样,用户就不用去关心那些过期的缓存项了。
Besides, we can add recordStats() when creating a cache object. Later we can monitor the cache usage by calling cache.stats()
Cache is created twice during the test, correspondingly, totalLoadTime (mesured in nano seconds) changed twice.
除此,我们还可以在创建缓存时使用 recordStats 来记录缓存的使用情况。之后用 cache 的 stats() 方法来观察。
测试过程中,我们的缓存产生了2次,totalLoadTime (纳秒为单位)也很好的佐证了这一点。
CacheStats{hitCount=0, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0} 10 10 CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=502324777, evictionCount=0} 10 10 10 CacheStats{hitCount=4, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=502324777, evictionCount=0} 10 10 CacheStats{hitCount=6, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=502324777, evictionCount=1} 10 10 10 CacheStats{hitCount=8, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=1002802169, evictionCount=1}
3. Caffeine cache 【Caffeine 本地缓存】
Caffeine cache is an optimized cache for Java 8. It has similar apis to guava but provide higher performance, which makes it easy to migrate from Gauva to Caffeine.
See this report for more details https://github.com/ben-manes/caffeine/wiki/Benchmarks
咖啡因缓存是专为Java 8而生的。使用上和Guava很像(很容易迁移),但它在多线程情况下性能更高。上面有一个跑分链接,比较了各种本地缓存的性能。
public class CaffeineCacheExample { static Cache<Integer, Integer> cache = Caffeine.newBuilder() .expireAfterWrite(2, TimeUnit.SECONDS) .recordStats() .build(); static LoadingCache<Integer, Integer> loadingCache = Caffeine.newBuilder() .expireAfterWrite(2, TimeUnit.SECONDS) .build(new CacheLoader<Integer, Integer>() { @Override public Integer load(Integer key) throws Exception { System.out.println("populate cache"); return key * 10; } }); static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); public static void main(String[] args) throws Exception { useNormalCache(); } static void useNormalCache() throws Exception { scheduler.scheduleWithFixedDelay(() -> System.out.println(cache.stats()), 0, 1, TimeUnit.SECONDS); for (int i = 0; i < 10; i++) { System.out.println(cache.get(0, k -> { try { Thread.sleep(500); } catch (InterruptedException ex) { //ignore } return 10; })); Thread.sleep(300); } } static void useLoadingCache() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(loadingCache.get(1)); Thread.sleep(300); } } }
There exists slight difference between Guava and Caffeine
使用上,有一点小小的区别
1) Builder: Guava use CacheBuilder to create new builder while Caffeine use Caffeine.
创建Builder时,Guava使用CacheBuilder,咖啡因使用Caffeine
2) Get method: Guava pass callable for get method
两者的Get方法第二个参数略有区别Guava 传入Callable, 内部可以抛出异常,无需写冗余的try catch,而咖啡因传入Function,必须手动捕获可能的异常
V get(K var1, Callable<? extends V> var2) throws ExecutionException;
Caffeine pass Function for get method
@CheckForNull V get(@Nonnull K var1, @Nonnull Function<? super K, ? extends V> var2);
Callable itself throws exception so we don‘t have to try catch the exception in the block while Function tolerates no exception.
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t);
Next Chapter, I‘ll compare the async cache.
下一节,博主会进一步比较异步缓存
A comparison of local caches (1) 【本地缓存之比较 (1)】