spring boot+spring cache 2 급 캐 시 구현(redis+caffeine)
내용 설명:
캐 시
간단 한 이해,캐 시 는 읽 기 느 린 미디어 에서 데 이 터 를 읽 어서 읽 기 빠 른 미디어 에 넣 는 것 입 니 다.예 를 들 어 디스크-->메모리.평소에 우 리 는 데이터 베 이 스 를 디스크 에 저장 합 니 다.예 를 들 어 데이터베이스.매번 데이터베이스 에서 읽 으 면 디스크 자체 의 IO 가 읽 기 속도 에 영향 을 주기 때문에 redis 와 같은 메모리 캐 시가 있 습 니 다.데 이 터 를 읽 어 메모리 에 넣 을 수 있 습 니 다.데 이 터 를 가 져 올 때 메모리 에서 데 이 터 를 직접 가 져 와 서 속 도 를 어느 정도 높 일 수 있 습 니 다.그러나 일반적으로 redis 는 단독 적 으로 클 러 스 터 로 배치 되 기 때문에 네트워크 IO 의 소모 가 있 을 수 있 습 니 다.redis 클 러 스 터 와 의 링크 는 연결 탱크 라 는 도구 가 있 지만 데이터 전송 에 도 어느 정도 소모 가 있 을 수 있 습 니 다.그래서 응용 내 캐 시 가 생 겼 습 니 다.예 를 들 어 caffeine.내 캐 시 에 조건 에 맞 는 데이터 가 있 을 때 직접 사용 할 수 있 으 며,네트워크 를 통 해 redis 에 가서 가 져 오지 않 아 도 2 급 캐 시 를 만 들 수 있 습 니 다.응용 내 캐 시 는 1 급 캐 시 라 고 하고 원 격 캐 시(예 를 들 어 redis)는 2 급 캐 시 라 고 합 니 다.
spring cache
캐 시 를 사용 할 때 보통 다음 과 같은 절차 입 니 다.
프로 세 스 도 에서 알 수 있 듯 이 캐 시 를 사용 하기 위해 기 존의 업무 처 리 를 바탕 으로 캐 시 에 대한 조작 을 많이 추 가 했 습 니 다.이 를 업무 코드 에 결합 시 키 면 중복 적 인 작업 이 많 고 코드 에 따라 업 무 를 이해 하 는 데 불리 합 니 다.
spring cache 는 spring-context 패키지 에서 제공 하 는 주석 기반 캐 시 구성 요소 로 표준 인 터 페 이 스 를 정의 합 니 다.이 인 터 페 이 스 를 실현 하면 방법 적 으로 주 해 를 추가 하여 캐 시 를 실현 할 수 있 습 니 다.이렇게 하면 캐 시 코드 와 업무 처리 가 결 합 된 문 제 를 피 할 수 있다.spring cache 의 실현 은 spring aop 에서 방법 절단면(MethodInterceptor)을 사용 하여 패 키 징 한 확장 입 니 다.물론 spring aop 도 Aspect 를 바탕 으로 이 루어 집 니 다.
spring cache 핵심 인 터 페 이 스 는 두 개 입 니 다.Cache 와 Cache Manager 입 니 다.
캐 시 인터페이스
캐 시 를 제공 하 는 구체 적 인 작업,예 를 들 어 캐 시 를 넣 고 읽 고 청소 하 는 것,spring 프레임 워 크 에서 기본적으로 제공 하 는 것 은 다음 과 같 습 니 다.
RedisCache 를 제외 하고 spring-data-redis 패키지 에 있 습 니 다.다른 기본 은 spring-context-support 패키지 에 있 습 니 다.
#Cache.java
package org.springframework.cache;
import java.util.concurrent.Callable;
public interface Cache {
// cacheName, , CacheManager Cache bean cacheName
String getName();
// , :RedisTemplate、com.github.benmanes.caffeine.cache.Cache<Object, Object>。 , bean,
Object getNativeCache();
// key , ValueWrapper, , , get
ValueWrapper get(Object key);
// key , ,
<T> T get(Object key, Class<T> type);
// key , valueLoader.call() @Cacheable 。 @Cacheable sync true 。 。
<T> T get(Object key, Callable<T> valueLoader);
// @Cacheable
void put(Object key, Object value);
// key 。 key
ValueWrapper putIfAbsent(Object key, Object value);
//
void evict(Object key);
// 。 , @Cacheable ,
void clear();
//
interface ValueWrapper {
//
Object get();
}
// {@link #get(Object, Callable)} ,
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {
private final Object key;
public ValueRetrievalException(Object key, Callable<?> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
public Object getKey() {
return this.key;
}
}
}
CacheManager 인터페이스주로 Cache 를 제공 하여 bean 의 생 성 을 실현 합 니 다.모든 응용 프로그램 에서 cacheName 을 통 해 Cache 를 격 리 할 수 있 고 모든 cacheName 은 하나의 Cache 에 대응 합 니 다.spring 프레임 워 크 에서 기본적으로 제공 하 는 실현 과 Cache 의 실현 은 모두 쌍 을 이 루어 나타 나 고 가방 구조 도 위의 그림 에 있 습 니 다.
#CacheManager.java
package org.springframework.cache;
import java.util.Collection;
public interface CacheManager {
// cacheName Cache bean, Cache bean, , ( Caffeine)
Cache getCache(String name);
// cacheName
Collection<String> getCacheNames();
}
상용 주해 설명@Cacheable:데 이 터 를 조회 하 는 방법 에 주로 사 용 됩 니 다.
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
// cacheNames,CacheManager Cache bean
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// key, SpEL 。 hashCode (SimpleKey)
String key() default "";
// key , SimpleKeyGenerator
String keyGenerator() default "";
// CacheManager
String cacheManager() default "";
//
String cacheResolver() default "";
// , SpEL , 。
String condition() default "";
// , SpEL ,
String unless() default "";
// , , false, Cache.get(key) ; true, Cache.get(key, Callable)
boolean sync() default false;
}
@CacheEvict:캐 시 를 지우 고 데 이 터 를 삭제 하 는 방법 에 주로 사 용 됩 니 다.Cacheable 보다 속성 이 두 개 더 많아 졌어 요.
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
// ... @Cacheable
// , false Cache.evict(key) ; true Cache.clear()
boolean allEntries() default false;
//
boolean beforeInvocation() default false;
}
spring boot 에 서 는 spring cache 를 통합 하고 다양한 캐 시 설정 을 제공 합 니 다.사용 할 때 어떤 캐 시(enum CacheType)를 사용 하 는 지 설정 하면 됩 니 다.
spring boot 에 확장 할 수 있 는 것 이 하나 더 추가 되 었 습 니 다.바로 CacheManager Customizer 인터페이스 입 니 다.이 인 터 페 이 스 를 사용자 정의 로 실현 한 다음 에 CacheManager 에 대해 설정 할 수 있 습 니 다.예 를 들 어:
package com.itopener.demo.cache.redis.config;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.data.redis.cache.RedisCacheManager;
public class RedisCacheManagerCustomizer implements CacheManagerCustomizer<RedisCacheManager> {
@Override
public void customize(RedisCacheManager cacheManager) {
// ,
cacheManager.setDefaultExpiration(1000);
cacheManager.setUsePrefix(false);
Map<String, Long> expires = new ConcurrentHashMap<String, Long>();
expires.put("userIdCache", 2000L);
cacheManager.setExpires(expires);
}
}
이 bean 불 러 오기:
package com.itopener.demo.cache.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author fuwei.deng
* @date 2017 12 22 10:24:54
* @version 1.0.0
*/
@Configuration
public class CacheRedisConfiguration {
@Bean
public RedisCacheManagerCustomizer redisCacheManagerCustomizer() {
return new RedisCacheManagerCustomizer();
}
}
자주 사용 하 는 캐 시 는 Redis 입 니 다.Redis 는 spring cache 인터페이스 에 대한 구현 은 spring-data-redis 패키지 에 있 습 니 다.여기 서 제 가 생각 하 는 RedisCache 실현 중의 결함 을 말씀 드 리 겠 습 니 다.
1.캐 시가 효력 을 잃 는 순간 캐 시 데 이 터 를 가 져 오 는 스 레 드 가 있 으 면 null 로 되 돌아 갈 수 있 습 니 다.이 유 는 RedisCache 가 다음 과 같은 절 차 를 밟 았 기 때 문 입 니 다.
2.RedisCacheManager 에서 빈 값 의 속성(cacheNullValues)을 기본적으로 false 로 저장 할 수 있 는 지 여부 입 니 다.즉,빈 값 을 저장 할 수 없습니다.그러면 캐 시 관통 위험 이 존재 합 니 다.결함 은 이 속성 이 final 형식 입 니 다.생 성 대상 은 구조 적 방법 으로 만 들 어 갈 수 있 기 때문에 캐 시 관통 을 피 하려 면 응용 프로그램 에서 RedisCacheManager 라 는 bean 을 설명 할 수 밖 에 없습니다.
3.RedisCacheManager 의 속성 은 설정 파일 을 통 해 직접 설정 할 수 없고 응용 프로그램 에서 CacheManager Customizer 인터페이스 만 설정 할 수 있 습 니 다.개인 적 으로 불편 합 니 다.
Caffeine
Caffine 은 구 글 의 오픈 소스 인 Guava 디자인 이념 을 바탕 으로 하 는 고성능 메모리 캐 시 로 자바 8 을 사용 하여 개발 되 었 으 며,spring boot 는 Caffine 을 도입 한 후 점차 Guava 의 통합 을 폐기 하 였 다.Caffine 소스 코드 및 소개 주소:caffeine
caffeine 은 다양한 캐 시 충전 전략,값 회수 전략 을 제공 하 는 동시에 캐 시 명중 횟수 등 통계 데 이 터 를 포함 하여 캐 시 최적화 에 큰 도움 을 줄 수 있 습 니 다.
caffeine 의 소 개 는 참고 할 수 있 습 니 다.https://www.jb51.net/article/134242.htm
간단하게 말하자면 caffeine 의 시간 기반 회수 전략 은 다음 과 같은 몇 가지 가 있다.
본인 이 처음에 언급 했 듯 이 redis 캐 시 를 사용 하 더 라 도 어느 정도 네트워크 전송 에 소모 가 있 을 수 있 습 니 다.실제 응용 에서 변경 빈도 가 매우 낮은 데이터 가 존재 하면 응용 내부 에 직접 캐 시 할 수 있 고 실시 간 요구 가 높 지 않 은 데이터 에 대해 내부 캐 시 를 일정 시간 사용 하여 redis 에 대한 방문 을 줄 일 수 있 습 니 다.응답 속도 향상
spring-data-redis 프레임 워 크 에서 redis 는 spring cache 의 실현 에 부족 하기 때문에 사용 할 때 문제 가 발생 할 수 있 기 때문에 원래 의 실현 을 바탕 으로 확장 하지 않 고 실현 방식 을 직접 참고 하여 Cache 와 CacheManager 인 터 페 이 스 를 실현 합 니 다.
또한 주의해 야 할 것 은 일반 응용 프로그램 에 여러 노드 가 배치 되 어 있 고 1 급 캐 시 는 응용 프로그램 에 있 는 캐 시 이기 때문에 데이터 업데이트 와 제거 시 모든 노드 에 캐 시 를 정리 하 는 작업 을 알려 야 합 니 다.이러한 효 과 를 실현 할 수 있 는 여러 가지 방법 이 있 습 니 다.예 를 들 어 zookeeper,MQ 등 입 니 다.그러나 redis 캐 시 를 사 용 했 기 때문에 redis 자체 가 구독/발표 기능 을 지원 하기 때문에 다른 구성 요소 에 의존 하지 않 고 redis 채널 을 사용 하여 다른 노드 에 캐 시 를 청소 하 는 작업 을 알 립 니 다.
다음은 spring boot+spring cache 에 대해 2 급 캐 시(redis+caffeine)를 실현 하 는 starter 패키지 절차 와 소스 코드 입 니 다.
properties 설정 속성 클래스 정의
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author fuwei.deng
* @date 2018 1 29 11:32:15
* @version 1.0.0
*/
@ConfigurationProperties(prefix = "spring.cache.multi")
public class CacheRedisCaffeineProperties {
private Set<String> cacheNames = new HashSet<>();
/** , true, */
private boolean cacheNullValues = true;
/** cacheName Cache , true*/
private boolean dynamic = true;
/** key */
private String cachePrefix;
private Redis redis = new Redis();
private Caffeine caffeine = new Caffeine();
public class Redis {
/** , , */
private long defaultExpiration = 0;
/** cacheName , , defaultExpiration */
private Map<String, Long> expires = new HashMap<>();
/** topic */
private String topic = "cache:redis:caffeine:topic";
public long getDefaultExpiration() {
return defaultExpiration;
}
public void setDefaultExpiration(long defaultExpiration) {
this.defaultExpiration = defaultExpiration;
}
public Map<String, Long> getExpires() {
return expires;
}
public void setExpires(Map<String, Long> expires) {
this.expires = expires;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
}
public class Caffeine {
/** , */
private long expireAfterAccess;
/** , */
private long expireAfterWrite;
/** , */
private long refreshAfterWrite;
/** */
private int initialCapacity;
/** , */
private long maximumSize;
/** , spring cache , */
// private long maximumWeight;
public long getExpireAfterAccess() {
return expireAfterAccess;
}
public void setExpireAfterAccess(long expireAfterAccess) {
this.expireAfterAccess = expireAfterAccess;
}
public long getExpireAfterWrite() {
return expireAfterWrite;
}
public void setExpireAfterWrite(long expireAfterWrite) {
this.expireAfterWrite = expireAfterWrite;
}
public long getRefreshAfterWrite() {
return refreshAfterWrite;
}
public void setRefreshAfterWrite(long refreshAfterWrite) {
this.refreshAfterWrite = refreshAfterWrite;
}
public int getInitialCapacity() {
return initialCapacity;
}
public void setInitialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
}
public long getMaximumSize() {
return maximumSize;
}
public void setMaximumSize(long maximumSize) {
this.maximumSize = maximumSize;
}
}
public Set<String> getCacheNames() {
return cacheNames;
}
public void setCacheNames(Set<String> cacheNames) {
this.cacheNames = cacheNames;
}
public boolean isCacheNullValues() {
return cacheNullValues;
}
public void setCacheNullValues(boolean cacheNullValues) {
this.cacheNullValues = cacheNullValues;
}
public boolean isDynamic() {
return dynamic;
}
public void setDynamic(boolean dynamic) {
this.dynamic = dynamic;
}
public String getCachePrefix() {
return cachePrefix;
}
public void setCachePrefix(String cachePrefix) {
this.cachePrefix = cachePrefix;
}
public Redis getRedis() {
return redis;
}
public void setRedis(Redis redis) {
this.redis = redis;
}
public Caffeine getCaffeine() {
return caffeine;
}
public void setCaffeine(Caffeine caffeine) {
this.caffeine = caffeine;
}
}
spring cache 에는 Cache 인 터 페 이 스 를 실현 하 는 추상 적 인 클래스 AbstractValue Adapting Cache 가 있 습 니 다.빈 값 의 포장 과 캐 시 값 의 포장 을 포함 하기 때문에 Cache 인 터 페 이 스 를 실현 하지 않 고 AbstractValue Adapting Cache 추상 류 를 직접 실현 합 니 다.
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.CacheRedisCaffeineProperties;
/**
* @author fuwei.deng
* @date 2018 1 26 5:24:11
* @version 1.0.0
*/
public class RedisCaffeineCache extends AbstractValueAdaptingCache {
private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCache.class);
private String name;
private RedisTemplate<Object, Object> redisTemplate;
private Cache<Object, Object> caffeineCache;
private String cachePrefix;
private long defaultExpiration = 0;
private Map<String, Long> expires;
private String topic = "cache:redis:caffeine:topic";
protected RedisCaffeineCache(boolean allowNullValues) {
super(allowNullValues);
}
public RedisCaffeineCache(String name, RedisTemplate<Object, Object> redisTemplate, Cache<Object, Object> caffeineCache, CacheRedisCaffeineProperties cacheRedisCaffeineProperties) {
super(cacheRedisCaffeineProperties.isCacheNullValues());
this.name = name;
this.redisTemplate = redisTemplate;
this.caffeineCache = caffeineCache;
this.cachePrefix = cacheRedisCaffeineProperties.getCachePrefix();
this.defaultExpiration = cacheRedisCaffeineProperties.getRedis().getDefaultExpiration();
this.expires = cacheRedisCaffeineProperties.getRedis().getExpires();
this.topic = cacheRedisCaffeineProperties.getRedis().getTopic();
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this;
}
@SuppressWarnings("unchecked")
@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(valueLoader.call());
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) {
if (!super.isAllowNullValues() && value == null) {
this.evict(key);
return;
}
long expire = getExpire();
if(expire > 0) {
redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.MILLISECONDS);
} else {
redisTemplate.opsForValue().set(getKey(key), toStoreValue(value));
}
push(new CacheMessage(this.name, key));
caffeineCache.put(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);
if(prevValue == null) {
long expire = getExpire();
if(expire > 0) {
redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.MILLISECONDS);
} else {
redisTemplate.opsForValue().set(getKey(key), toStoreValue(value));
}
push(new CacheMessage(this.name, key));
caffeineCache.put(key, toStoreValue(value));
}
}
return toValueWrapper(prevValue);
}
@Override
public void evict(Object key) {
// redis , caffeine , caffeine redis caffeine
redisTemplate.delete(getKey(key));
push(new CacheMessage(this.name, key));
caffeineCache.invalidate(key);
}
@Override
public void clear() {
// redis , caffeine , caffeine redis caffeine
Set<Object> keys = redisTemplate.keys(this.name.concat(":"));
for(Object key : keys) {
redisTemplate.delete(key);
}
push(new CacheMessage(this.name, null));
caffeineCache.invalidateAll();
}
@Override
protected Object lookup(Object key) {
Object cacheKey = getKey(key);
Object value = caffeineCache.getIfPresent(key);
if(value != null) {
logger.debug("get cache from caffeine, the key is : {}", cacheKey);
return value;
}
value = redisTemplate.opsForValue().get(cacheKey);
if(value != null) {
logger.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey);
caffeineCache.put(key, value);
}
return value;
}
private Object getKey(Object key) {
return this.name.concat(":").concat(StringUtils.isEmpty(cachePrefix) ? key.toString() : cachePrefix.concat(":").concat(key.toString()));
}
private long getExpire() {
long expire = defaultExpiration;
Long cacheNameExpire = expires.get(this.name);
return cacheNameExpire == null ? expire : cacheNameExpire.longValue();
}
/**
* @description
* @author fuwei.deng
* @date 2018 1 31 3:20:28
* @version 1.0.0
* @param message
*/
private void push(CacheMessage message) {
redisTemplate.convertAndSend(topic, message);
}
/**
* @description
* @author fuwei.deng
* @date 2018 1 31 3:15:39
* @version 1.0.0
* @param key
*/
public void clearLocal(Object key) {
logger.debug("clear local cache, the key is : {}", key);
if(key == null) {
caffeineCache.invalidateAll();
} else {
caffeineCache.invalidate(key);
}
}
}
CacheManager 인터페이스 구현
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.CacheRedisCaffeineProperties;
/**
* @author fuwei.deng
* @date 2018 1 26 5:24:52
* @version 1.0.0
*/
public class RedisCaffeineCacheManager implements CacheManager {
private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCacheManager.class);
private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>();
private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;
private RedisTemplate<Object, Object> redisTemplate;
private boolean dynamic = true;
private Set<String> cacheNames;
public RedisCaffeineCacheManager(CacheRedisCaffeineProperties cacheRedisCaffeineProperties,
RedisTemplate<Object, Object> redisTemplate) {
super();
this.cacheRedisCaffeineProperties = cacheRedisCaffeineProperties;
this.redisTemplate = redisTemplate;
this.dynamic = cacheRedisCaffeineProperties.isDynamic();
this.cacheNames = cacheRedisCaffeineProperties.getCacheNames();
}
@Override
public Cache getCache(String name) {
Cache cache = cacheMap.get(name);
if(cache != null) {
return cache;
}
if(!dynamic && !cacheNames.contains(name)) {
return cache;
}
cache = new RedisCaffeineCache(name, redisTemplate, caffeineCache(), cacheRedisCaffeineProperties);
Cache oldCache = cacheMap.putIfAbsent(name, cache);
logger.debug("create cache instance, the cache name is : {}", name);
return oldCache == null ? cache : oldCache;
}
public com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache(){
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
if(cacheRedisCaffeineProperties.getCaffeine().getExpireAfterAccess() > 0) {
cacheBuilder.expireAfterAccess(cacheRedisCaffeineProperties.getCaffeine().getExpireAfterAccess(), TimeUnit.MILLISECONDS);
}
if(cacheRedisCaffeineProperties.getCaffeine().getExpireAfterWrite() > 0) {
cacheBuilder.expireAfterWrite(cacheRedisCaffeineProperties.getCaffeine().getExpireAfterWrite(), TimeUnit.MILLISECONDS);
}
if(cacheRedisCaffeineProperties.getCaffeine().getInitialCapacity() > 0) {
cacheBuilder.initialCapacity(cacheRedisCaffeineProperties.getCaffeine().getInitialCapacity());
}
if(cacheRedisCaffeineProperties.getCaffeine().getMaximumSize() > 0) {
cacheBuilder.maximumSize(cacheRedisCaffeineProperties.getCaffeine().getMaximumSize());
}
if(cacheRedisCaffeineProperties.getCaffeine().getRefreshAfterWrite() > 0) {
cacheBuilder.refreshAfterWrite(cacheRedisCaffeineProperties.getCaffeine().getRefreshAfterWrite(), TimeUnit.MILLISECONDS);
}
return cacheBuilder.build();
}
@Override
public Collection<String> getCacheNames() {
return this.cacheNames;
}
public void clearLocal(String cacheName, Object key) {
Cache cache = cacheMap.get(cacheName);
if(cache == null) {
return ;
}
RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache;
redisCaffeineCache.clearLocal(key);
}
}
redis 메시지 발표/구독,전송 메시지 클래스
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support;
import java.io.Serializable;
/**
* @author fuwei.deng
* @date 2018 1 29 1:31:17
* @version 1.0.0
*/
public class CacheMessage implements Serializable {
/** */
private static final long serialVersionUID = 5987219310442078193L;
private String cacheName;
private Object key;
public CacheMessage(String cacheName, Object key) {
super();
this.cacheName = cacheName;
this.key = key;
}
public String getCacheName() {
return cacheName;
}
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
public Object getKey() {
return key;
}
public void setKey(Object key) {
this.key = key;
}
}
redis 메 시 지 를 감청 하려 면 MessageListener 인터페이스 가 필요 합 니 다.
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @author fuwei.deng
* @date 2018 1 30 5:22:33
* @version 1.0.0
*/
public class CacheMessageListener implements MessageListener {
private final Logger logger = LoggerFactory.getLogger(CacheMessageListener.class);
private RedisTemplate<Object, Object> redisTemplate;
private RedisCaffeineCacheManager redisCaffeineCacheManager;
public CacheMessageListener(RedisTemplate<Object, Object> redisTemplate,
RedisCaffeineCacheManager redisCaffeineCacheManager) {
super();
this.redisTemplate = redisTemplate;
this.redisCaffeineCacheManager = redisCaffeineCacheManager;
}
@Override
public void onMessage(Message message, byte[] pattern) {
CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());
logger.debug("recevice a redis topic message, clear local cache, the cacheName is {}, the key is {}", cacheMessage.getCacheName(), cacheMessage.getKey());
redisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey());
}
}
spring boot 설정 클래스 추가
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.CacheMessageListener;
import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.RedisCaffeineCacheManager;
/**
* @author fuwei.deng
* @date 2018 1 26 5:23:03
* @version 1.0.0
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties(CacheRedisCaffeineProperties.class)
public class CacheRedisCaffeineAutoConfiguration {
@Autowired
private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;
@Bean
@ConditionalOnBean(RedisTemplate.class)
public RedisCaffeineCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
return new RedisCaffeineCacheManager(cacheRedisCaffeineProperties, redisTemplate);
}
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate<Object, Object> redisTemplate,
RedisCaffeineCacheManager redisCaffeineCacheManager) {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory());
CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCacheManager);
redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(cacheRedisCaffeineProperties.getRedis().getTopic()));
return redisMessageListenerContainer;
}
}
resources/META-INF/spring.factories 파일 에 spring boot 설정 스 캔 추가
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.CacheRedisCaffeineAutoConfiguration
이제 Maven 도입 으로 사용 할 수 있 습 니 다.
<dependency>
<groupId>com.itopener</groupId>
<artifactId>cache-redis-caffeine-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
</dependency>
시작 클래스 에@EnableCashing 주 해 를 추가 하고 캐 시가 필요 한 방법 에@Cacheable 주 해 를 추가 합 니 다.
package com.itopener.demo.cache.redis.caffeine.service;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.itopener.demo.cache.redis.caffeine.vo.UserVO;
import com.itopener.utils.TimestampUtil;
@Service
public class CacheRedisCaffeineService {
private final Logger logger = LoggerFactory.getLogger(CacheRedisCaffeineService.class);
@Cacheable(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager")
public UserVO get(long id) {
logger.info("get by id from db");
UserVO user = new UserVO();
user.setId(id);
user.setName("name" + id);
user.setCreateTime(TimestampUtil.current());
return user;
}
@Cacheable(key = "'cache_user_name_' + #name", value = "userNameCache", cacheManager = "cacheManager")
public UserVO get(String name) {
logger.info("get by name from db");
UserVO user = new UserVO();
user.setId(new Random().nextLong());
user.setName(name);
user.setCreateTime(TimestampUtil.current());
return user;
}
@CachePut(key = "'cache_user_id_' + #userVO.id", value = "userIdCache", cacheManager = "cacheManager")
public UserVO update(UserVO userVO) {
logger.info("update to db");
userVO.setCreateTime(TimestampUtil.current());
return userVO;
}
@CacheEvict(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager")
public void delete(long id) {
logger.info("delete from db");
}
}
properties 파일 에서 redis 설정 은 redis 를 사용 하 는 것 과 같 습 니 다.2 급 캐 시 설정 을 추가 할 수 있 습 니 다.
#
spring.cache.multi.caffeine.expireAfterAccess=5000
spring.cache.multi.redis.defaultExpiration=60000
#spring cache
spring.cache.cache-names=userIdCache,userNameCache
#redis
#spring.redis.timeout=10000
#spring.redis.password=redispwd
#redis pool
#spring.redis.pool.maxIdle=10
#spring.redis.pool.minIdle=2
#spring.redis.pool.maxActive=10
#spring.redis.pool.maxWait=3000
#redis cluster
spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006
spring.redis.cluster.maxRedirects=3
넓히다개인 적 으로 redisson 의 포장 이 더 편리 하 다 고 생각 합 니 다.
원본 코드 다운로드
starter 디렉토리:springboot/itopener-parent/spring-boot-starters-parent/cache-redis-caffeine-spring-boot-starter-parent
예제 코드 디 렉 터 리:springboot/itopener-parent/demo-parent/demo-cache-redis-caffeine
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
thymeleaf로 HTML 페이지를 동적으로 만듭니다 (spring + gradle)지난번에는 에서 화면에 HTML을 표시했습니다. 이번에는 화면을 동적으로 움직여보고 싶기 때문에 입력한 문자를 화면에 표시시키고 싶습니다. 초보자의 비망록이므로 이상한 점 등 있으면 지적 받을 수 있으면 기쁩니다! ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.