티스토리 뷰

오늘은 SpringBoot에서 Redis와 EHCache를 동시에 활용해 캐싱을 구현하는 방법에 대해 간단히 알아보겠습니다.


1. Redis 란?

Redis는 인메모리 데이터 저장소로 빠른 읽기/쓰기 성능을 제공하는 NoSQL 입니다. 키-값 저장 구조를 사용하며, 원격 서버에서 실행 가능해 분산 환경에 적합합니다.

사용처

  • 세션 관리: 사용자 세션 데이터를 빠르게 저장/조회
  • 캐싱: 자주 조회되는 데이터를 메모리에 저장해 DB 부하 감소
  • 실시간 데이터 처리: 순위표, 실시간 분석 등

2. EHCache 란?

EHCache는 JVM내에서 동작하는 인메모리 캐싱 라이브러리로, JCache(JSR-107) 표준을 지원합니다. 로컬 환경에서 가볍고 빠르게 캐싱을 구현할 수 있습니다.

사용처

  • 로컬 캐싱: 서버 내에서 빠르게 접근해야 하는 데이터 저장
  • 소규모 애플리케이션: 외부 캐시 서버 없이 간단한 캐싱 필요 시
  • Spring 통합: @Cacheable, @CachePut 어노테이션으로 쉽게 캐싱 적용

3. Redis vs EHCache

  • Redis: 외부 서버로 실행되며, 분산 환경과 대규모 데이터에 강력. 네트워크 오버헤드가 있음.
  • EHCache: JVM 내에서 동작해 빠르고 가볍지만, 로컬 환경에 국한. 메모리 사용량 주의 필요.

4. 예제 소스 작성

build.gradle

dependencies {
    // Spring Boot Starters
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-cache'

    // Cache Providers
    implementation 'org.ehcache:ehcache:3.10.8'
    implementation 'javax.cache:cache-api:1.1.1'

    // Lombok
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'

    // Testing
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
  • SpringBoot 3.5.5 와 JDK 21을 기반으로 Redis와 EHCache 그리고 Lombok 에 관련된 의존성을 추가합니다.

 

SpringBootApplication

package com.cache.cachemanager;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CacheManagerApplication {
  public static void main(String[] args) {
      SpringApplication.run(CacheManagerApplication.class, args);
  }
}
  • 어플리케이션 소스에 @EnableCaching 을 추가하여 캐싱을 활성화 합니다.

 

application.yml

server:
  port: 1220

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
  cache:
    type: none # Redis 와 EHCache 를 수동으로 설정
  • Redis 정보를 정의하고, cache-type 을 none 으로 설정합니다.

 

RedisConfig.java

package com.cache.cachemanager.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

  // RedisConnectionFactory 생략 => application.yml 의 spring.data.redis 설정 기반 자동 제공

  @Bean
  public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    redisTemplate.setKeySerializer(new StringRedisSerializer()); // Key 는 문자열로 직렬화
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // Value 는 JSON 형식으로 직렬화
    return redisTemplate;
  }
}
  • RedisConnectionFatory 를 주입받아 Redis 서버에 연결하고 키는 String, 값은 Json 형식으로 직렬화합니다.

 

EHCacheConfig.java

package com.cache.cachemanager.config;

import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.jsr107.Eh107Configuration;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.cache.CacheManager;
import javax.cache.Caching;
import java.time.Duration;

@Configuration
public class EHCacheConfig {

  @Bean
  public JCacheCacheManager ehCacheManager(){
    EhcacheCachingProvider provider =
      (EhcacheCachingProvider) Caching.getCachingProvider("org.ehcache.jsr107.EhcacheCachingProvider"); // Provider 를 통해 EHCache 를 JCache 로 연결

    CacheManager cacheManager = provider.getCacheManager(); // CacheManager 생성

    // EHCache 설정 : 힙 메모리 100개 엔트리, TTL 5분
    CacheConfiguration<String, String> ehcacheConfig = CacheConfigurationBuilder
      .newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(100)) // 캐시 설정 (Key, Value 타입 및 최대 엔트리 수)
      .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(5)))
      .build();

    // JCache Configuration 으로 변환
    javax.cache.configuration.Configuration<String, String> cacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(ehcacheConfig);

    cacheManager.createCache("ehcache", cacheConfiguration); // 캐시 생성

    return new JCacheCacheManager(cacheManager); // JCacheCacheManager 로 래핑하여 반환
  }
}
  • jsr107 을 명시해 JCache 프로바이더를 로드하고 "ehcache" 라는 이름으로 캐시를 생성합니다.

 

RedisService.java

package com.cache.cachemanager.service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
@RequiredArgsConstructor
public class RedisCacheService {

  private final RedisTemplate<String, String> redisTemplate;

  public void setValue(String key, String value) {
    redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(10)); // TTL 10분
  }

  public String getValue(String key) {
    return redisTemplate.opsForValue().get(key);
  }
}
  • RedisTemplate 을 주입받아, set/get 함수를 작성합니다.

 

EHCacheService.java

package com.cache.cachemanager.service;

import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;


@Service
public class EHCacheService {

  @CachePut(value = "ehcache", key = "#key" ) // 캐시 이름(EHCacheConfig.java 에서 설정)과 키 지정
  public String setValue(String key, String value){
    // 메서드를 무조건 실행하고, 반환값을 지정된 캐시 이름과 키에 저장
    return value;
  }

  @Cacheable(value = "ehcache", key = "#key" )
  public String getValue(String key){
    // 캐시에 데이터가 있으면 메서드를 실행하지 않고 캐시 값을 반환
    return "No Cache"; // 캐시 미스 시
  }
}
  • @CachePut 을 사용하여 반환값을 지정된 캐시 이름과 키에 저장합니다.
  • @Cacheable 을 사용하여 캐시된 데이터가 있으면 바로 반환하고 없다면 메서드를 수행합니다.

 

CacheController.java

package com.cache.cachemanager.controller;

import com.cache.cachemanager.service.EHCacheService;
import com.cache.cachemanager.service.RedisCacheService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/cache")
public class CacheController {

  private final RedisCacheService redisCacheService;
  private final EHCacheService ehCacheService;

  @PutMapping("/redis/{key}")
  public String setRedisCache(@PathVariable String key, @RequestParam String value){
    redisCacheService.setValue(key, value);
    return value;
  }

  @GetMapping("/redis/{key}")
  public String getRedisCache(@PathVariable String key) {
    return redisCacheService.getValue(key);
  }

  @PutMapping("/ehcache/{key}")
  public String setEHCache(@PathVariable String key, @RequestParam String value) {
    return ehCacheService.setValue(key, value);
  }

  @GetMapping("/ehcache/{key}")
  public String getEHCache(@PathVariable String key) {
    return ehCacheService.getValue(key);
  }
}
  • Redis PUT: /api/cache/redis/name?value=홍길동
  • Redis GET: /api/cache/redis/name
  • Cache PUT: /api/cache/ehcache/name?value=김철수
  • Cache GET: /api/cache/ehcache/name

5. 마무리

Redis와 EHCache는 각각의 강점을 살려 캐싱 전략을 다양화할 수 있는 강력한 도구입니다. Redis는 분산 환경에서, EHCache는 로컬환경에서 유용하며, SpringBoot와 통합하면 쉽게 적용 가능합니다. 감사합니다.

최근에 올라온 글
Total
Today
Yesterday