Caffeine是基于JAVA 1.8 Version的高性能缓存库。Caffeine提供的内存缓存使用参考Google guava的API。Caffeine是基于Google Guava Cache设计经验上改进的成果。
<dependency>
<groupId>com.github.ben-manes.caffeinegroupId>
<artifactId>caffeineartifactId>
<version>2.8.2version>
dependency>
caffeine 有4中初始化方法:
Cache<Key, Object> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 查找一个缓存元素, 没有查找到的时候返回null
Object graph = cache.getIfPresent(key);
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
graph = cache.get(key, k -> createObject(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.invalidate(key);
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key)); //传入根据key获取数据的方法
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
Graph graph = cache.get(key);
// 批量查找缓存,如果缓存不存在则生成缓存元素
Map<Key, Graph> graphs = cache.getAll(keys);
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.buildAsync();
// 查找缓存元素,如果不存在,则异步生成
CompletableFuture<Graph> graph = cache.get(key, k -> createExpensiveGraph(key));
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 你可以选择: 去异步的封装一段同步操作来生成缓存元素
.buildAsync(key -> createExpensiveGraph(key));
// 你也可以选择: 构建一个异步缓存元素操作并返回一个future
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
// 查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Graph> graph = cache.get(key);
// 批量查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
//设置缓存大小
cache..maximumSize(10_000);
//在最后一次写入缓存后开始计时,在指定的时间后过期。(优先级高)
cache.expireAfterWrite(long, TimeUnit)
//在最后一次读或写后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。
cache.expireAfterAccess(long, TimeUnit)
//传入自定义的过期处理类
cache.expire();
//这个参数是 LoadingCache 和 AsyncLoadingCache 的才会有的。在刷新的时候如果查询缓存元素,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。
cache.refreshAfterWrite(long, TimeUnit);
//设置缓存被删除时的监听器
cache.removalListener();
//开启监控功能,使用 cache.status()会返回 CacheStats 对象:
//CacheStats: hitRate()查询缓存的命中率,evictionCount(): 被驱逐的缓存数量,averageLoadPenalty(): 新值被载入的平均耗时
cache.recordStats()
更多使用方法参考: https://zhuanlan.zhihu.com/p/329684099
一级缓存: caffeine, 二级缓存: redis。使用caffeine一级缓存能够减去 查询redis时网络传输耗时。
实现方案: 自定义缓存管理器整合redis和caffeine,查询时从redis内的缓存放入caffeine,修改或删除缓存时通过redis发布订阅功能所有应用删除caffeine的缓存。
mvn 依赖:
<dependency>
<groupId>com.github.ben-manes.caffeinegroupId>
<artifactId>caffeineartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
继承 AbstractValueAdaptingCache 实现自定义的缓存类
@Slf4j
public class RedisCaffeineCache extends AbstractValueAdaptingCache {
//cacheName
private final String name;
private final RedisTemplate redisTemplate;
private final Cache<Object, Object> caffeineCache;
private final CacheConfigProperties properties;
public RedisCaffeineCache(String name, RedisTemplate redisTemplate, Cache caffeineCache, CacheConfigProperties properties) {
//是否缓存null,可防止缓 存穿透
super( properties.getAllowNull() );
this.name = name;
this.redisTemplate = redisTemplate;
this.caffeineCache = caffeineCache;
this.properties = BeanUtil.copyProperties(properties,CacheConfigProperties.class);
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this;
}
/**
* 如果是分布式需要用分布式锁
*/
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
//先查询缓存
Object value = lookup(key);
if(value != null) return (T) value;
//一级缓存和二级缓存都没有,设置缓存
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
value = lookup(key);
if(value != null) return (T) value;
value = valueLoader.call();
Object storeValue = toStoreValue(value);
//设置缓存
put(key, storeValue);
return (T) value;
} catch (Exception e) {
try {
Class<?> c = Class.forName("org.springframework.cache.Cache$ValueRetrievalException");
Constructor<?> constructor = c.getConstructor(Object.class, Callable.class, Throwable.class);
RuntimeException exception = (RuntimeException) constructor.newInstance(key, valueLoader, e.getCause());
throw exception;
} catch (Exception e1) {
throw new IllegalStateException(e1);
}
} finally {
lock.unlock();
}
}
@Override
public void put(Object key, Object value) {
//如果key是null 并且不支持null 直接删除
if ( value == null && !super.isAllowNullValues() ) {
this.evict(key);
return;
}
if( properties.getExpireMillis() > 0) {
redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), properties.getExpireMillis(), TimeUnit.MILLISECONDS);
} else {
redisTemplate.opsForValue().set(getKey(key), toStoreValue(value));
}
caffeineCache.put(getKey(key), value);
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
Object cacheKey = getKey(key);
Object prevValue = null;
// 考虑使用分布式锁,或者将redis的setIfAbsent改为原子性操作
synchronized (key) {
prevValue = redisTemplate.opsForValue().get(cacheKey);
boolean putSuccess = false;
if( properties.getExpireMillis() > 0 ) {
putSuccess = redisTemplate.opsForValue().setIfAbsent(getKey(key), toStoreValue(value), properties.getExpireMillis(), TimeUnit.MILLISECONDS);
} else {
putSuccess = redisTemplate.opsForValue().setIfAbsent(getKey(key), toStoreValue(value));
}
//如果设置成功
if( putSuccess ) {
publish( new DelCacheMessage( this.name,key ) );
caffeineCache.put(key, toStoreValue(value));
}
}
return toValueWrapper(prevValue);
}
/**
* 剔除某个缓存
*/
@Override
public void evict(Object key) {
final Object cacheKey = getKey(key);
// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
redisTemplate.delete( cacheKey );
//发布缓存 让所有caffeine清除缓存
publish( new DelCacheMessage( name,cacheKey ) );
//清除本地缓存
caffeineCache.invalidate(cacheKey);
}
/**
* 清除所有缓存
*/
@Override
public void clear() {
// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
Set<Object> keys = redisTemplate.keys(this.name.concat(":"));
for(Object key : keys) redisTemplate.delete(key);
//发布消息让所有caffeine清除缓存
publish( new DelCacheMessage(name,"ALL") );
//清除本地缓存
caffeineCache.invalidateAll();
}
/**
* 查询缓存
*/
@Override
protected Object lookup (Object key ) {
//获取缓存key
Object cacheKey = getKey(key);
//查询一级缓存(caffeine)
Object value = caffeineCache.getIfPresent(cacheKey);
if(value != null) {
log.info("从caffeine内获取缓存,key: {},value: {}", cacheKey,value);
return value;
}
//查询二级缓存
value = redisTemplate.opsForValue().get(cacheKey);
if(value != null) {
log.info("从redis获取缓存并放入caffeine,key: {} ,value: {}", cacheKey,value);
caffeineCache.put(cacheKey, value);
}
return value;
}
/**
* 获取缓存key
*/
private String getKey(Object key) {
String cacheKey = key.toString();
return this.name.concat(":").concat( cacheKey );
}
/**
* @description 缓存变更时通知其他节点清理本地缓存
* @param message 要删除的缓存
*/
private void publish( DelCacheMessage message) {
redisTemplate.convertAndSend(properties.getChannelTopic(), message);
}
/**
* @description 清理本地缓存
*/
public void clearLocal(Object key) {
log.info("清除本地caffeine缓存, key : {}", key);
if(key == null) {
caffeineCache.invalidateAll();
} else {
caffeineCache.invalidate(key);
}
}
}
消息包装类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DelCacheMessage {
private String cacheName;
private Object cacheKey;
}
发布订阅消费者
@Slf4j
public class CacheDelListener implements MessageListener {
private RedisTemplate redisTemplate;
private Cache caffeineCache;
public CacheDelListener(RedisTemplate redisTemplate, Cache caffeineCache){
this.redisTemplate = redisTemplate;
this.caffeineCache = caffeineCache;
}
@Override
public void onMessage(Message message, byte[] pattern) {
//接收到消息说明要删除本地的缓存
DelCacheMessage delMessage = (DelCacheMessage)redisTemplate.getValueSerializer().deserialize(message.getBody());
Object channel = redisTemplate.getStringSerializer().deserialize(message.getChannel());
log.info("删除本地缓存,channel: {{}}, cacheKey:{{}}",channel,delMessage.getCacheKey());
//删除caffeine的缓存
caffeineCache.invalidate( delMessage.getCacheKey() );
}
}
CacheConfigProperties
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CacheConfigProperties {
/**
* 是否允许控制,主要用于缓存穿透
*/
private Boolean allowNull = true;
/**
* 缓存过期时间
*/
private Long expireMillis = 60 * 1000L;
/**
* redis发布订阅的主题,用于删除缓存
*/
private String channelTopic = "cache:redis:caffeine:topic";
}
@Slf4j
public class RedisCaffeineCacheManager implements CacheManager {
private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>();
private CacheConfigProperties properties;
private RedisTemplate redisTemplate;
private com.github.benmanes.caffeine.cache.Cache caffeineCache;
public RedisCaffeineCacheManager(RedisTemplate redisTemplate,
com.github.benmanes.caffeine.cache.Cache caffeineCache,
CacheConfigProperties cacheRedisCaffeineProperties){
this.properties = cacheRedisCaffeineProperties;
this.redisTemplate = redisTemplate;
this.caffeineCache = caffeineCache;
}
@Override
public Cache getCache(String name) {
Cache cache = cacheMap.get(name);
if( cache != null ) return cache;
cache = new RedisCaffeineCache(name,redisTemplate,this.caffeineCache,this.properties);
//放入map
cacheMap.put(name,cache);
return cache;
}
@Override
public Collection<String> getCacheNames() {
return this.cacheMap.keySet();
}
public com.github.benmanes.caffeine.cache.Cache getCaffeineCache() {
return caffeineCache;
}
public CacheConfigProperties getProperties() {
return properties;
}
}
public class RedisConfig {
private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
private static final GenericJackson2JsonRedisSerializer JACKSON_SERIALIZER = new GenericJackson2JsonRedisSerializer();
//redis的缓存管理器
@Primary
@Bean("redisCacheManager")
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
//设置缓存过期时间
RedisCacheConfiguration redisCacheCfg = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(STRING_SERIALIZER))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(JACKSON_SERIALIZER));
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheCfg)
.build();
}
//配置redis缓存
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
// key序列化
redisTemplate.setKeySerializer(STRING_SERIALIZER);
// value序列化
redisTemplate.setValueSerializer(JACKSON_SERIALIZER);
// Hash key序列化
redisTemplate.setHashKeySerializer(STRING_SERIALIZER);
// Hash value序列化
redisTemplate.setHashValueSerializer(JACKSON_SERIALIZER);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
//配置发布订阅 消费者
public RedisMessageListenerContainer cacheDelListenerContainer(RedisConnectionFactory redisConnectionFactory,MessageListenerAdapter cacheDelListenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer( );
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(cacheDelListenerAdapter,new ChannelTopic("cache:redis:caffeine:topic") );
return container;
}
@Bean
public MessageListenerAdapter cacheDelListenerAdapter( @Autowired RedisTemplate redisTemplate,@Autowired RedisCaffeineCacheManager cacheManager ){
Cache caffeineCache = cacheManager.getCaffeineCache();
CacheDelListener cacheDelListener = new CacheDelListener(redisTemplate, caffeineCache);
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter();
messageListenerAdapter.setDelegate( cacheDelListener );
return messageListenerAdapter;
}
//自定义的缓存管理器
@Bean("level2CacheManager")
public RedisCaffeineCacheManager redisCaffeineCacheManager( @Autowired RedisTemplate redisTemplate){
CacheConfigProperties properties = new CacheConfigProperties(true,60000L,"cache:redis:caffeine:topic");
return new RedisCaffeineCacheManager(redisTemplate,caffeineCache(),properties);
}
//配置 caffeine缓存
public com.github.benmanes.caffeine.cache.Cache<String, Object> caffeineCache(){
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
// 访问后过期时间,单位毫秒
cacheBuilder.expireAfterAccess(7000, TimeUnit.MILLISECONDS);
// 写入后过期时间,单位毫秒
cacheBuilder.expireAfterWrite(6000, TimeUnit.MILLISECONDS);
//初始化大小
cacheBuilder.initialCapacity(10);
//最大缓存对象个数,超过此数量时之前放入的缓存将失效
cacheBuilder.maximumSize( 1000 );
// 写入后刷新时间,单位毫秒
cacheBuilder.refreshAfterWrite(8000, TimeUnit.MILLISECONDS);
cacheBuilder.removalListener((RemovalListener<String, Object>) (key, value, cause) -> {
log.info("caffeine缓存过期,key:{}",key);
});
return cacheBuilder.softValues().build( cacheLoader() );
}
//caffeine用到的类
public CacheLoader<String, Object> cacheLoader() {
CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
return null;
}
// 重写这个方法将oldValue值返回回去,进而刷新缓存
@Override
public Object reload(String key, Object oldValue) throws Exception {
return oldValue;
}
};
return cacheLoader;
}
}