真正的缓存之王,Google Guava 只是弟弟
点击关注公众号,Java干货及时送达
作者:rickiyang
来源:www.cnblogs.com/rickiyang/p/11074158.html
FIFO:先进先出,在这种淘汰算法中,先进入缓存的会先被淘汰,会导致命中率很低。
LRU:最近最少使用算法,每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。仍然有个问题,如果有个数据在 1 分钟访问了 1000次,再后 1 分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。
LFU:最近最少频率使用,利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。
当数据的访问模式不随时间变化的时候,LFU的策略能够带来最佳的缓存命中率。然而LFU有两个缺点:
首先,它需要给每个记录项维护频率信息,每次访问都需要更新,这是个巨大的开销;
其次,如果数据访问模式随时间有变,LFU的频率信息无法随之变化,因此早先频繁访问的记录可能会占据缓存,而后期访问较多的记录则无法被命中。
因此,大多数的缓存设计都是基于LRU或者其变种来进行的。相比之下,LRU并不需要维护昂贵的缓存记录元信息,同时也能够反应随时间变化的数据访问模式。然而,在许多负载之下,LRU依然需要更多的空间才能做到跟LFU一致的缓存命中率。因此,一个“现代”的缓存,应当能够综合两者的长处。
<dependency>
<groupId>com.github.ben-manes.caffeinegroupId>
<artifactId>caffeineartifactId>
<version>2.6.2version>
dependency>
/** * 手动加载 * @param key * @return */public Object manulOperator(String key) { Cache<String, Object> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .expireAfterAccess(1, TimeUnit.SECONDS) .maximumSize(10) .build(); //如果一个key不存在,那么会进入指定的函数生成value Object value = cache.get(key, t -> setValue(key).apply(key)); cache.put("hello",value); //判断是否存在如果不存返回null Object ifPresent = cache.getIfPresent(key); //移除一个key cache.invalidate(key); return value;}public Function setValue(String key){ return t -> key + "value";}
/** * 同步加载 * @param key * @return */public Object syncOperator(String key){ LoadingCache<String, Object> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .build(k -> setValue(key).apply(key)); return cache.get(key);}public Function<String, Object> setValue(String key){ return t -> key + "value";}
/** * 异步加载 * * @param key * @return */public Object asyncOperator(String key){ AsyncLoadingCache<String, Object> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .buildAsync(k -> setAsyncValue(key).get()); return cache.get(key);}public CompletableFuture<Object> setAsyncValue(String key){ return CompletableFuture.supplyAsync(() -> { return key + "value"; });}
// 根据缓存的计数进行驱逐LoadingCache cache = Caffeine.newBuilder() .maximumSize(10000) .build(key -> function(key));// 根据缓存的权重来进行驱逐(权重只是用于确定缓存大小,不会用于决定该缓存是否被驱逐)LoadingCache cache1 = Caffeine.newBuilder() .maximumWeight(10000) .weigher(key -> function1(key)) .build(key -> function(key));
// 基于固定的到期策略进行退出LoadingCache cache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .build(key -> function(key));LoadingCache cache1 = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> function(key));// 基于不同的到期策略进行退出LoadingCache cache2 = Caffeine.newBuilder() .expireAfter(new Expiry() { @Override public long expireAfterCreate(String key, Object value, long currentTime) { return TimeUnit.SECONDS.toNanos(seconds); } @Override public long expireAfterUpdate(@Nonnull String s, @Nonnull Object o, long l, long l1) { return 0; } @Override public long expireAfterRead(@Nonnull String s, @Nonnull Object o, long l, long l1) { return 0; } }).build(key -> function(key));
// 当key和value都没有引用时驱逐缓存LoadingCache cache = Caffeine.newBuilder() .weakKeys() .weakValues() .build(key -> function(key));// 当垃圾收集器需要释放内存时驱逐LoadingCache cache1 = Caffeine.newBuilder() .softValues() .build(key -> function(key));
Cache<String, Object> cache = Caffeine.newBuilder()
.removalListener((String key, Object value, RemovalCause cause) ->
System.out.printf("Key %s was removed (%s)%n", key, cause))
.build();
LoadingCache<String, Object> cache2 = Caffeine.newBuilder()
.writer(new CacheWriter<String, Object>() {
public void write(String key, Object value) {
// 写入到外部存储
}
public void delete(String key, Object value, RemovalCause cause) {
// 删除外部存储
}
})
.build(key -> function(key));
Cache<String, Object> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.recordStats()
.build();
hitRate(): 返回缓存命中率evictionCount(): 缓存回收数量averageLoadPenalty(): 加载新值的平均时间
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>com.github.ben-manes.caffeinegroupId>
<artifactId>caffeineartifactId>
<version>2.6.2version>
dependency>
class SingleDatabaseApplication { public static void main(String[] args) { SpringApplication.run(SingleDatabaseApplication.class, args); }}
spring.cache.cache-names=cache1
spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s
spring:
cache:
type: caffeine
:
userCache
caffeine:
spec: maximumSize=1024,refreshAfterWrite=60s
import com.github.benmanes.caffeine.cache.CacheLoader;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author: rickiyang * @date: 2019/6/15 * @description: */ class CacheConfig { /** * 相当于在构建LoadingCache对象的时候 build()方法中指定过期之后的加载策略方法 * 必须要指定这个Bean,refreshAfterWrite=60s属性才生效 * @return */ public CacheLoader<String, Object> cacheLoader() { CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() { public Object load(String key) throws Exception { return null; } // 重写这个方法将oldValue值返回回去,进而刷新缓存 @Override public Object reload(String key, Object oldValue) throws Exception { return oldValue; } }; return cacheLoader; }}
initialCapacity=[integer]: 初始的缓存空间大小maximumSize=[long]: 缓存的最大条数maximumWeight=[long]: 缓存的最大权重expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存weakKeys: 打开key的弱引用weakValues:打开value的弱引用softValues:打开value的软引用recordStats:开发统计功能注意:expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。maximumSize和maximumWeight不可以同时使用weakValues和softValues不可以同时使用
package com.rickiyang.learn.cache;import com.github.benmanes.caffeine.cache.CacheLoader;import com.github.benmanes.caffeine.cache.Caffeine;import org.apache.commons.compress.utils.Lists;import org.springframework.cache.CacheManager;import org.springframework.cache.caffeine.CaffeineCache;import org.springframework.cache.support.SimpleCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;/** * @author: rickiyang * @date: 2019/6/15 * @description: */ class CacheConfig { /** * 创建基于Caffeine的Cache Manager * 初始化一些key存入 * @return */ public CacheManager caffeineCacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); ArrayList caches = Lists.newArrayList(); List list = setCacheBean(); for(CacheBean cacheBean : list){ caches.add(new CaffeineCache(cacheBean.getKey(), Caffeine.newBuilder().recordStats() .expireAfterWrite(cacheBean.getTtl(), TimeUnit.SECONDS) .maximumSize(cacheBean.getMaximumSize()) .build())); } cacheManager.setCaches(caches); return cacheManager; } /** * 初始化一些缓存的 key * @return */ private List setCacheBean() { List list = Lists.newArrayList(); CacheBean userCache = new CacheBean(); userCache.setKey("userCache"); userCache.setTtl(60); userCache.setMaximumSize(10000); CacheBean deptCache = new CacheBean(); deptCache.setKey("userCache"); deptCache.setTtl(60); deptCache.setMaximumSize(10000); list.add(userCache); list.add(deptCache); return list; } class CacheBean { private String key; private long ttl; private long maximumSize; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public long getTtl() { return ttl; } public void setTtl(long ttl) { this.ttl = ttl; } public long getMaximumSize() { return maximumSize; } public void setMaximumSize(long maximumSize) { this.maximumSize = maximumSize; } }}
@Cacheable 触发缓存入口(这里一般放在创建和获取的方法上,@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存)
@CacheEvict 触发缓存的eviction(用于删除的方法上)
@CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
@Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
@CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
@Cacheable:它的注解的方法是否被执行取决于Cacheable中的条件,方法很多时候都可能不被执行。
@CachePut:这个注解不会影响方法的执行,也就是说无论它配置的条件是什么,方法都会被执行,更多的时候是被用到修改上。
public Cacheable { /** * 要使用的cache的名字 */ ("cacheNames") String[] value() default {}; /** * 同value(),决定要使用那个/些缓存 */ ("value") String[] cacheNames() default {}; /** * 使用SpEL表达式来设定缓存的key,如果不设置默认方法上所有参数都会作为key的一部分 */ String key() default ""; /** * 用来生成key,与key()不可以共用 */ String keyGenerator() default ""; /** * 设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字 */ String cacheManager() default ""; /** * 使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用 */ String cacheResolver() default ""; /** * 使用SpEL表达式设定出发缓存的条件,在方法执行前生效 */ String condition() default ""; /** * 使用SpEL设置出发缓存的条件,这里是方法执行完生效,所以条件中可以有方法执行后的value */ String unless() default ""; /** * 用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法 * 则只允许一个线程通过去执行方法 */ boolean sync() default false;}
package com.rickiyang.learn.cache;import com.rickiyang.learn.entity.User;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;/** * @author: rickiyang * @date: 2019/6/15 * @description: 本地cache */ class UserCacheService { /** * 查找 * 先查缓存,如果查不到,会查数据库并存入缓存 * @param id */ (value = "userCache", key = "#id", sync = true) public void getUser(long id){ //查找数据库 } /** * 更新/保存 * @param user */ @CachePut(value = "userCache", key = "#user.id") public void saveUser(User user){ //todo 保存数据库 } /** * 删除 * @param user */ @CacheEvict(value = "userCache",key = "#user.id") public void delUser(User user){ //todo 保存数据库 }}
@Cacheable(key = "targetClass + methodName +#p0")