Caffeine本地缓存
Caffeine是一种高性能的缓存库,是基于Java 8的最佳(最优)缓存框架。
基于Google的Guava Cache,Caffeine提供一个性能卓越的本地缓存(local cache) 实现, 也是SpringBoot内置的本地缓存实现。(Caffeine性能是Guava Cache的6倍)
优点
本地缓存,基于物理机内存,读取速度快
缺点
缓存在多节点之间无法同步,存储容量限制
Redis分布式缓存
Redis缓存是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
优点
分布式缓存多节点公用,存储容量可扩展
缺点
每次获取缓存都会有网络开销
多级缓存设计
查询:Caffeine作为一级缓存,Redis作为二级缓存,对于指定的高热key,优先访问一级缓存,没有再去访问二级缓存,再没有去执行接口 更新删除:通过redis订阅topic,通知所有节点去删除本地缓存和redis缓存 优点:提高缓存速度,减少网络io 缺点:多节点下缓存的维护与同步成本增加,对于会更新的key,不可靠性加大
实现
新建自定义缓存
自定义cache,继承AbstractValueAdaptingCache,内部维护Caffeine缓存,通过标识字段usedCaffeineCache来确定当前缓存是否使用Caffeine缓存,usedCaffeineCache字段通过@CacheAble中name字段获取
/**
* 获取缓存
* @param key
* @return
*/
@Override
protected Object lookup(Object key) {
Object cacheKey = getKey(key);
Object value;
if (usedCaffeineCache) {
value = getCaffeineValue(key);
if (value != null) {
if (log.isDebugEnabled()){
log.debug("get cache from caffeine, the key is : {}", cacheKey);
}
return value;
}
}
value = getRedisValue(key);
if (value != null) {
if (log.isDebugEnabled()) {
log.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey);
}
setCaffeineValue(key, value);
}
return value;
}
/**
* 清除缓存
* @param key
*/
@Override
public void evict(Object key) {
// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
stringKeyRedisTemplate.delete(getKey(key));
if (this.usedCaffeineCache) {
push(new CacheMessage(this.name, key));
caffeineCache.invalidate(key);
}
}
/**
* 添加缓存
* @param key
* @param value
*/
@Override
public void put(Object key, Object value) {
if (!super.isAllowNullValues() && value == null) {
this.evict(key);
return;
}
doPut(key, value);
}
private void doPut(Object key, Object value) {
value = toStoreValue(value);
Duration expire = getExpire();
setRedisValue(key, value, expire);
if (this.usedCaffeineCache) {
push(new CacheMessage(this.name, key));
setCaffeineValue(key, value);
}
}
自定义cacheManage,在本地维护一个CacheMap
private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>();
@Override
public Cache getCache(String name) {
Cache cache = cacheMap.get(name);
if (cache != null) {
return cache;
}
if (!dynamic && !cacheNames.contains(name)) {
return null;
}
cache = cacheFactory.createCache(name);
Cache oldCache = cacheMap.putIfAbsent(name, cache);
log.debug("create cache instance, the cache name is : {}", name);
return oldCache == null ? cache : oldCache;
}
新建DefaultCacheFactory来创建自定义的缓存,通过解析name去判定是否允许使用Caffeine一级缓存,默认不使用
/**
* 创建缓存
* @param name
* @return
*/
@Override
public RedisCaffeineCache createCache(String name) {
boolean usedCaffeineCache = false;
String[] redisConfigs = RedisUtil.replaceName(name, cacheConfigProperties.getRedis().getDelimiter());
if (redisConfigs.length>2){
String cacheType = redisConfigs[2];
if (!StrUtil.isBlank(cacheType) && CacheType.RedisCaffeineCache.getCacheType().equals(cacheType)){
usedCaffeineCache = true;
}
}
return new RedisCaffeineCache(name, stringKeyRedisTemplate, cacheConfigProperties,usedCaffeineCache);
}
注入自定义CacheManage
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(RedisTemplate.class)
public RedisCaffeineCacheManager cacheManager(CacheConfigProperties cacheConfigProperties,
RedisTemplate<String, Object> redisTemplate,
ObjectProvider<RedisCaffeineCacheManagerCustomizer> cacheManagerCustomizers,
CacheFactory cacheFactory) {
RedisCaffeineCacheManager cacheManager = new RedisCaffeineCacheManager(cacheConfigProperties, redisTemplate,
cacheFactory);
cacheManagerCustomizers.orderedStream().forEach(customizer -> customizer.customize(cacheManager));
return cacheManager;
}
使用
name规则:name+过期时间+是否使用一级缓存 不使用一级缓存
@Cacheable(value = "get#6m",key = "#key")
@GetMapping("/get")
public String get(String key){
return key;
}
每次都会去访问redis获得缓存
1667978526.697602 [0 127.0.0.1:50098] "GET" "get#6m:test"
1667978526.898507 [0 127.0.0.1:50099] "GET" "get#6m:test"
1667978527.093476 [0 127.0.0.1:50100] "GET" "get#6m:test"
1667978527.307849 [0 127.0.0.1:50101] "GET" "get#6m:test"
1667978527.496456 [0 127.0.0.1:50102] "GET" "get#6m:test"
1667978527.728409 [0 127.0.0.1:50103] "GET" "get#6m:test"
1667978527.933313 [0 127.0.0.1:50104] "GET" "get#6m:test"
1667978528.132622 [0 127.0.0.1:50106] "GET" "get#6m:test"
1667978528.379935 [0 127.0.0.1:50105] "GET" "get#6m:test"
使用一级缓存
@Cacheable(value = "get#6m#1",key = "#key")
@GetMapping("/get")
public String get(String key){
return key;
}
只会访问一次redis,其余都是获取本地缓存
1667978655.462160 [0 127.0.0.1:50585] "GET" "get#6m#1:test"
1667978655.469164 [0 127.0.0.1:50588] "SETEX" "get#6m#1:test" "360" "\"test\""
1667978655.474989 [0 127.0.0.1:50587] "PUBLISH" "cache:redis:caffeine:topic" "{\"@class\":\"com.jwk.common.redis.support.CacheMessage\",\"cacheName\":\"get#6m#1\",\"key\":\"test\"}"
1667978655.744241 [0 127.0.0.1:50589] "GET" "get#6m#1:test"
详细代码地址:gitee.com/musi1996/jc…