Cache란?

나중에 할 요청 결과를 미리 저장 해 두었다가 빠르게 서비스 해 주는 것

  • Look aside Cache(일반적으로 많이 쓰는 방식)
    • DB를 가기 전에 Cache에 먼저 데이터가 있는지 확인 해본다.
    • 만약 Cache에 데이터가 있다면 캐시 데이터를 읽고, 없다면 DB를 읽은 후 Cache에 저장 & 결과 리턴
  • Write Back(로그를 DB에 저장하는 경우, 쓰기 연산이 많은 경우)
    • 캐시에 먼저 저장했다가, 특정 시점마다 DB에 저장하는 방식
    • ex. 걸리는 시간(배치 작업) - DB insert 쿼리를 1번씩 * 500번 >> insert쿼리 500개 합친 것 * 1번
    • 단점: 장애가 생기면 데이터가 사라질 위험이 있다. 왜냐면 리부팅 되면 메모리는 사라지기 때문

 

캐시의 대상이 되는 정보들은 아래와 같다.

 

  • 단순한, 또는 단순한 구조의 정보를 -> 정보의 단순성 
  • 반복적으로 동일하게 제공해야 하거나 -> 빈번한 동일요청의  반복
  • 정보의 변경주기가 빈번하지 않고, 단위처리 시간이 오래걸리는 정보이고 -> 높은 단위처리비용
  • 정보의 최신화가 반드시 실시간으로 이뤄지지 않아도 서비스 품질에 영향을 거의 주지 않는 정보 
  • 예) 실시간 검색어, 방문자수/조회수/추천수, 1회성 인증정보, 공지사항/Q&A 등

 

 

Redis란?

REDIS(REmote Dictionary Server)는 메모리 기반의 “키-값” 구조 데이터 관리 시스템이며, 모든 데이터를 메모리에 저장하고 조회하기에 빠른 Read, Write 속도를 보장하는 비 관계형 데이터베이스이다.

Redis가 타 캐시 시스템(ex. MemCache 등)과 다른 특징은 아래와 같다.

  1. Redis는 List, Set, Sorted Set, Hash 등과 같은 Collection을 지원합니다.
  2. Race condition에 빠질 수 있는 것을 방지함
    • Redis는 Single Thread
    • 따라서 Atomic 보장
  3. persistence를 지원하여 서버가 꺼지더라도 다시 데이터를 불러들일 수 있습니다.

 

Embedded Redis를 이용하여 @Service 메소드를 캐시 함수로 사용하기

프로그램 구조

 

1. gradle 추가

implementation 'org.apache.commons:commons-lang3:3.4'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'it.ozimov:embedded-redis:0.7.2'

 

2. config class 추가 - 주의

spring:
  profiles:
    active: local
  cache:
    type: redis
  redis:
    host: localhost
    port: 6375
public class CacheKey {

  public static final int DEFAULT_EXPIRE_SEC = 600; // 10 minutes
  public static final String STOCK = "stock";
  public static final int STOCK_EXPIRE_DAY = 1;
}
@Profile("local")
@Configuration
public class EmbeddedRedisConfig {

  @Value("${spring.redis.port}")
  private int redisPort;

  private RedisServer redisServer;

  @PostConstruct
  public void redisServer() {
    redisServer = new RedisServer(redisPort);
    redisServer.start();
  }

  @PreDestroy
  public void stopRedis() {
    if (redisServer != null) {
      redisServer.stop();
    }
  }
}
@RequiredArgsConstructor
@EnableCaching
@Configuration
public class RedisConfig {

  private final ObjectMapper objectMapper;

  @Autowired
  public RedisConfig() {
    this.objectMapper = new ObjectMapper();
  }

  @Bean(name = "cacheManager")
  public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    var jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(StockResult.class);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);


    RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
        .disableCachingNullValues()
        .entryTtl(Duration.ofSeconds(CacheKey.DEFAULT_EXPIRE_SEC))
        .computePrefixWith(CacheKeyPrefix.simple())
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(
            jackson2JsonRedisSerializer
        ));

    Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
    cacheConfigurations.put(CacheKey.STOCK, RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofDays(CacheKey.STOCK_EXPIRE_DAY)));

    return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory).cacheDefaults(configuration)
        .withInitialCacheConfigurations(cacheConfigurations).build();
  }
}

 

여기서 주의해야 할 점은 나는 레디스에 저장 할 값이 객체이기 때문에 Serializer를레디스에서 제공해 주는 jackson2JsonRedisSerializer를 사용했다. 이 때, 아래와 같이 ObjectMapper가 StockResult라는 객체를 Serialize 해 줄 수 있도록 코드를 추가한다.

var jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(StockResult.class);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

 

3. @Service 메소드에 @Cacheable 추가하기

@Cacheable(value = CacheKey.STOCK, key = "#stockRequestDto.stockSymbol+#stockRequestDto.dateToday", unless = "#result == null")
  public StockResult calculateStockResult(StockRequestDto stockRequestDto) {
    ...
  }

옵션의 value에는 저장시 키값을, key에는 키 생성시 추가로 덧붙일 파라미터 정보를 선언한다.

나의 경우 캐시키는 stock::AAPL2020-09-27과 같은 형태로 생성된다.

unless = “#result == null”는 메서드 결과가 null이 아닌 경우에만 캐싱하도록 하는 옵션이다.

 

4. 메인 Application에 @EnableCaching 어노테이션 추가하기

@EnableCaching
@SpringBootApplication
public class StockApplication {

  public static void main(String[] args) {
    SpringApplication.run(StockApplication.class, args);
  }

}

 

이렇게 만들어 본 후 포스트맨으로 테스트 해 보니,

맨 처음에 캐시에 데이터가 없는 경우를 제외하고는 결과 값을 바르게 바로바로 리턴하는 것을 확인할 수 있었다.


참고

https://www.youtube.com/watch?v=mPB2CZiAkKM

https://daddyprogrammer.org/post/3870/spring-rest-api-redis-caching/

 

SpringBoot2로 Rest api 만들기(15) – Redis로 api 결과 캐싱(Caching)처리

이번 장에서는 지금까지 개발한 api에 캐시를 적용해 보도록 하겠습니다. 캐시란 자주 사용되는 데이터를 메모리에 저장하고 반환하여 하드디스크의 원본데이터를 거치지 않게 함으로서 리소스

daddyprogrammer.org

https://sabarada.tistory.com/103

 

[Redis] 캐시(Cache)와 Redis

[Redis] 캐시(Cache)와 Redis [Redis] Redis의 기본 명령어 [Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisTemplate 편 [Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisRepository..

sabarada.tistory.com

https://javaengine.tistory.com/entry/SpringBoot2%EB%A1%9C-Rest-api-%EB%A7%8C%EB%93%A4%EA%B8%B015-%E2%80%93-Redis%EB%A1%9C-api-%EA%B2%B0%EA%B3%BC-%EC%BA%90%EC%8B%B1Caching%EC%B2%98%EB%A6%AC

 

SpringBoot2로 Rest api 만들기(15) – Redis로 api 결과 캐싱(Caching)처리

이번 장에서는 지금까지 개발한 api에 캐시를 적용해 보도록 하겠습니다. 캐시란 자주 사용되는 데이터를 메모리에 저장하고 반환하여 하드디스크의 원본데이터를 거치지 않게 함으로서 리소스

javaengine.tistory.com

 

+ Recent posts