개요
Java Spring Boot 환경에서 레디스 캐시 설정과 레디스 장애 발생시 대응의 위한 설정
Spring Cache 어노테이션 종류
|어노테이션|설명| |——|—| |@Cacheable| cache 등록 | |@CachePut| cache 갱신 | |@CacheEvict| cache 삭제 |
dependencies 추가
org.springframework.boot 2.4.2
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
properties 추가
# cache
spring.cache.type=redis
CacheConfig.java - 1분단위 캐시 설정
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean
@Override
public CacheManager cacheManager(){
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(1L));
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
}
}
사용예시
@Cacheable(value = "testCache",cacheManager="cacheManager")
public Map<String,Object> testCache(){
...
}
// 저장된 캐시 정보
// testCache::SimpleKey []
@Cacheable(value = "testCacheKey",key="#keyName",cacheManager="cacheManager")
public Map<String,Object> testCacheKey(String keyName){
...
}
// 저장된 캐시 정보
// testCacheKey::keyName
문제점
레디스 서버가 다운되는 경우 서비스 에러가 발생되어 원활한 서비스 지원이 불가능합니다.
여러 검색내용에서는 로컬 스토리지와 레디스 두개를 다 사용하는 것을 권장하는 부분도 있지만 테스트 진행을 위해
레디스 서버 장애 발생 시 오류가 발생하지 않고 바로 DB 조회를 할수 있도록 설정을 추가 하였습니다.
#장애대응 설정추가
dependencies 추가
ext {
set('springCloudVersion', "2020.0.2")
}
dependencies {
...
implementation group: 'org.springframework.session', name: 'spring-session-data-redis', version: '2.5.0'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix:2.2.8.RELEASE'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
properties 추가
spring.redis.timeout=3000
spring.redis.connect-timeout=3000
spring.redis.database=0
RedisConfig.java
public class RedisConfig {
@Bean
public RedisSessionRepository sessionRepository(RedisOperations<String, Object> sessionRedisOperations) {
RedisSessionRepository redisSessionRepository = new RedisSessionRepository(sessionRedisOperations);
return redisSessionRepository;
}
@Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
}
CacheConfig.java
@Configuration
@EnableCaching(proxyTargetClass = true)
public class CacheConfig extends CachingConfigurerSupport{
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean
@Override
public CacheManager cacheManager(){
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(1));
return new HystrixCacheManager(RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build());
}
}
Netflix / Hystrix
- https://github.com/Netflix/Hystrix
- 테스트 진행을 위해 아래의 내용만 우선 사용
HystrixCache.java
public class HystrixCache implements Cache {
private final Cache delegate;
public HystrixCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public String getName() {
return null;
}
@Override
public Object getNativeCache() {
return null;
}
@Override
public ValueWrapper get(Object key) {
return new HystrixCacheGetCommand(delegate, key).execute();
}
@Override
public <T> T get(Object key, Class<T> type) {
return null;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return null;
}
@Override
public void put(Object key, Object value) {
new HystrixCachePutCommand(delegate, key, value).execute();
}
@Override
public void evict(Object key) {
new HystrixCacheEvictCommand(delegate, key).execute();
}
@Override
public void clear() {
}
}
HystrixCacheEvictCommand.java
public class HystrixCacheEvictCommand extends HystrixCommand<Object> {
private final Cache delegate;
private final Object key;
public HystrixCacheEvictCommand(Cache delegate, Object key) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("testGroupKey"))
.andCommandKey(HystrixCommandKey.Factory.asKey("cache-evict"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.defaultSetter()
.withExecutionTimeoutInMilliseconds(500)
.withCircuitBreakerErrorThresholdPercentage(50)
.withCircuitBreakerRequestVolumeThreshold(5)
.withMetricsRollingStatisticalWindowInMilliseconds(20000)));
this.delegate = delegate;
this.key = key;
}
@Override
protected Object run() {
delegate.evict(key);
return null;
}
@Override
protected Object getFallback() {
log.warn("evict fallback called, circuit is {}", super.circuitBreaker.isOpen() ? "opened" : "closed");
return null;
}
}
HystrixCacheGetCommand.java
public class HystrixCacheGetCommand extends HystrixCommand<Cache.ValueWrapper> {
private final Cache delegate;
private final Object key;
public HystrixCacheGetCommand(Cache delegate, Object key) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("testGroupKey"))
.andCommandKey(HystrixCommandKey.Factory.asKey("cache-get"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.defaultSetter()
.withExecutionTimeoutInMilliseconds(500)
.withCircuitBreakerErrorThresholdPercentage(50)
.withCircuitBreakerRequestVolumeThreshold(5)
.withMetricsRollingStatisticalWindowInMilliseconds(20000)));
this.delegate = delegate;
this.key = key;
}
@Override
protected Cache.ValueWrapper run() {
return delegate.get(key);
}
@Override
protected Cache.ValueWrapper getFallback() {
log.warn("get fallback called, circuit is {}", super.circuitBreaker.isOpen() ? "opened" : "closed");
return null;
}
}
HystrixCacheManager.java
public class HystrixCacheManager implements CacheManager {
private final CacheManager delegate;
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>();
public HystrixCacheManager(CacheManager delegate) {
this.delegate = delegate;
}
@Override
public Cache getCache(String name) {
return cacheMap.computeIfAbsent(name,key->new HystrixCache(delegate.getCache(key)));
}
@Override
public Collection<String> getCacheNames() {
return delegate.getCacheNames();
}
}
HystrixCachePutCommand.java
public class HystrixCachePutCommand extends HystrixCommand<Object> {
private final Cache delegate;
private final Object key;
private final Object value;
public HystrixCachePutCommand(Cache delegate, Object key, Object value) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("testGroupKey"))
.andCommandKey(HystrixCommandKey.Factory.asKey("cache-put"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.defaultSetter()
.withExecutionTimeoutInMilliseconds(500)
.withCircuitBreakerErrorThresholdPercentage(50)
.withCircuitBreakerRequestVolumeThreshold(5)
.withMetricsRollingStatisticalWindowInMilliseconds(20000)));
this.delegate = delegate;
this.key = key;
this.value = value;
}
@Override
protected Object run() {
delegate.put(key, value);
return null;
}
@Override
protected Object getFallback() {
log.warn("put fallback called, circuit is {}", super.circuitBreaker.isOpen() ? "opened" : "closed");
return null;
}
}
테스트 결과
Redis에 캐시 데이터 생성 확인 후 레디스 서버를 다운시킨 결과 DB조회로 대체하여 정상적으로 서비스 유지 확인 완료