안녕하세요, 이번엔 Spring Boot Rest API 서비스를 만들고, Redis를 이용하여 API 캐시(cache)를 적용해보는 샘플을 만들어 보도록 하겠습니다.
API 캐시란
우선 캐시(Cache)란, 한번 처리한 데이터를 임시로 저장소에 저장하는 것으로, 이 임시 데이터를 동일하거나 유사 요청이 왔을 경우 저장소에서 바로 읽어와서 응답을 하여 성능 및 응답속도 향상을 위한 기술입니다.
API 서비스에서 요청(Request)이 왔을 경우, 연산을 수행하거나 DB의 데이터를 불러오거나 3rd Party 시스템에 인터페이스 하는 등 특정 작업을 하여 데이터를 생성 후 다시 전달(Response)을 하게되는데 캐시를 이용해 특정 요청을 저장소에 임시로 저장해놨다가 이후 동일한 응답을 해도 되는 요청이 왔을 경우 별도 연산 없이 바로 저장소에서 데이터를 가지고 응답을 하여 성능과 응답 시간을 개선할 수 있습니다.
대규모의 Backend 서비스에서 캐싱은 필수라고 할 수 있습니다. 다만, 별도의 연산 수행없이 동일한 응답 값을 전달해도 되는 대상을 선정하고, 어떤 기준으로 캐시를 적용할 것인지, 캐시의 유지 기간 (TTL,Time To Live), 저장공간 등 설계 시 고려해야 할 사항도 많고 적용에 어려움도 많아 보입니다.
Spring Boot Rest API 서비스 구현
Redis Cache 적용을 위해, 아주 간단한 Spring Boot Rest API 서비스를 구현해보겠습니다. 우선 아래와 같이 패키지 및 자바 클래스 파일을 생성해줍니다.
bulid.gradle
dependency로 spring-boot-starter-web (lombok : 옵션)을 추가합니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
TestController.java
@RestController
public class TestController {
@Autowired
TestService svc;
@GetMapping("/getTest")
public TestVo getData(@RequestParam String id ){
return svc.getTestSvc(id);
}
}
TestService.java
@Service
public class TestService {
public TestVo getTestSvc(String id){
TestVo tvo = new TestVo();
tvo.setId(id);
tvo.setText( id + "님, 안녕하세요~!");
System.out.println("[id:" + id + "] Service 에서 연산을 수행합니다");
return tvo;
}
}
TestVo.java
@Data // Lombok 미적용 시, Getter/Setter 메서드 추가
public class TestVo {
private String id;
private String text;
}
위와 같이 작성 후 Run을 하여 프로젝트를 실행해보겠습니다.
아래와 같이 getTest란 주소로 id 파라미터를 추가하여 Get 호출을 하면, json 형태로 간략히 응답이 오는 샘플 API를 작성하였습니다.
http://localhost:8080/getTest?id=ori
{
"id": "ori",
"text": "ori님, 안녕하세요~!"
}
Redis Cache 적용
캐시의 임시 저장소로 Redis를 사용을 할 예정인데요, 여기서 Redis란 오픈소스 Key-Value 구조의 인메모리 저장소로 빠르고 간편하여 많은 곳에서 사용 중 이고 특히 캐싱 서비스의 저장소로 많이 사용 중입니다. 만약 로컬에 설치가 안되어 있다면 미리 설치를 해야 합니다. (Windows 설치 파일 다운로드 : https://github.com/microsoftarchive/redis/releases)
Redis가 설치가 되었다면, 먼저 아래와 같이 bulid.gradle 파일에 spring-boot-starter-data-redis 디펜던시를 추가하고, application.yml 파일에 Redis 접속 정보를 입력합니다.
bulid.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
application.yml
spring:
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
RedisConfig.java
위에서 spring-boot-starter-data-redis Dependency를 추가한 것으로 Redis를 통한 캐시사용은 가능하지만, 설정 커스터마이징을 위해서 @Configuration 어노테이션과 함께 Bean에 Redis CacheManager 설정을 아래와 같이 등록해줍니다.
캐싱할 API의 응답값 데이터 타입이 String 타입이 아닌 위의 예제처럼 Json 등의 다른 데이터 타입이라면 Serializer 에러가 발생합니다. 거의 대부분의 API 서비스는 응답을 json 형태로 할 텐데, 디폴트인 JdkSerializationRedisSerializer는 Json 데이터를 직렬 화하여 Redis에 저장이 가능한 형태로 변경을 할 수 없음으로 json 포맷 지원을 위한 Jackson2JsonRedisSerializer 또는 GenericJackson2JsonRedisSerializer로 변경을 합니다. 여기서는 GenericJackson2JsonRedisSerializer로 변경을 하였습니다. Serializer 변경 말고, Redis에 저장될 키값의 Prefix 설정이나, TTL (Time To Live) 설정, Null Value 캐싱 등도 설정이 가능합니다.
또한 @EnableCaching 어노테이션을 이 Config 파일 혹은 SpringBootApplication에 추가하여 캐싱을 사용하겠다고 선언합니다.
@EnableCaching
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@SuppressWarnings("deprecation")
@Bean
public CacheManager cacheManager() {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
.prefixKeysWith("Test:") // Key Prefix로 "Test:"를 앞에 붙여 저장
.entryTtl(Duration.ofMinutes(30)); // 캐시 수명 30분
builder.cacheDefaults(configuration);
return builder.build();
}
}
TestController.java
@Cacheable 어노테이션을 Service 혹은 Controller에 추가하여 캐싱 사용을 등록할 수 있지만, 여기서는 아래와 같이 Controller에 추가합니다.
@Cacheable(value = "TestVo", key = "#id", cacheManager = "cacheManager", unless = "#id == ''", condition = "#id.length > 2")
- value = "TestVo" : 저장될 value로 API의 리턴 데이터인 TestVo 객체로 선언
- key = "#id" : 이 API에서 id에 따라 응답값이 달라지므로 저장될 Key로 id 파라미터 값을 선언
- cacheManager = "cacheManager" : 위의 config에서 작성한 cacheManager 사용
- unless = "#id == ''" : id가 "" 일때 캐시를 저장하지 않음
- condition = "#id.length > 2" : id의 lengrh가 3 이상일 때만 캐시 저장
@RestController
public class TestController {
@Autowired
TestService svc;
@Cacheable(value = "TestVo", key = "#id", cacheManager = "cacheManager", unless = "#id == ''", condition = "#id.length > 2")
@GetMapping("/getTest")
public TestVo getData(@RequestParam String id ){
return svc.getTestSvc(id);
}
}
API + Redis Cache 테스트
위와 같이 간략히 Spring Boot Rest API 샘플 예제와 Redis Cache 적용 샘플 예제를 구현을 하였는데, 테스트를 진행해보면 정상적으로 동작하는 것을 확인할 수 있습니다.
- http://localhost:8080/getTest?id=ori
1회 호출 시 아래와 같이 콘솔에 Service에서 발생한 로그가 출력되고, 2회 호출 시 로그가 출력이 안되지만 리턴은 오는 것을 확인할 수 있습니다.
여기서 Service에는 실제 구현은 하지 않았지만, DB 조회가 될 수도 있고 다른 서비스로 인터페이스를 통해 데이터를 가져올 수도 있습니다. 한 번의 호출로 캐시 데이터를 저장하고 2번째부터는 Service 메서드 수행 없이 바로 캐시에 있는 데이터로 응답을 했기 때문에, 속도 및 성능이 향상됨을 확인할 수 있습니다.
redis-cli에 접속하여 확인해보면 캐시 데이터가 아래와 같이 정상적으로 저장되는 것을 알 수 있습니다.
- http://localhost:8080/getTest?id=
만약 unless, condition 조건에 걸리도록 위와 같이 호출을 하면, service단의 로그가 호출하는 데로 출력되는 것이 확인 가능합니다.
'[IT] Redis' 카테고리의 다른 글
Redis(레디스) 설치 (Windows) 및 기본 명령어, 자료구조 (0) | 2023.02.09 |
---|---|
[Docker] Spring Boot + Redis Docker Compose로 다중 컨테이너 실행 예제 (0) | 2022.04.14 |
댓글