1. API Gateway란?
API Gateway는 클라이언트와 백엔드 서비스 사이에서 중재자 역할을 하는 소프트웨어 계층이다. 모든 API 호출을 단일 진입점으로 받아 적절한 백엔드 서비스로 라우팅하고, 필요한 데이터를 집계하여 클라이언트에 통합된 방식으로 전달한다. 주요 기능으로는 요청 라우팅, 요청 필터링, 프로토콜 변환, 인증 및 권한 부여, 로깅, 모니터링, 속도 제한, 로드 밸런싱 등이 있다. API Gateway는 마이크로서비스 아키텍처에서 특히 중요하며, API 관리를 단순화하고 보안을 강화하는 데 도움을 준다. 또한, 클라이언트 인터페이스와 백엔드 구현을 분리하여 개발자가 개별 서비스 구축에 집중할 수 있게 한다.
2. Spring Cloud Gateway란?
Spring Cloud Gateway는 Spring 생태계에서 사용되는 API Gateway 솔루션이다. Spring Cloud Netflix 패키지의 일부로, 마이크로서비스 아키텍처에서 널리 사용된다. 비동기 및 논 블로킹 방식으로 동작하여 높은 성능과 확장성을 제공한다. Predicate와 Filter를 조합하여 유연한 요청 처리가 가능하다. Spring Boot와 긴밀하게 통합되어 있어 개발자 경험이 우수하며, WebSocket과 같은 장기 연결도 지원한다. 또한 Netty를 기반으로 하여 높은 동시성 처리를 할 수 있다.
3. Spring Cloud Gateway 필터링
필터를 구현하려면 GlobalFilter 또는 GatewayFilter 인터페이스를 구현하고,
Filter 메서드를 오버라이드 해야 한다.
Global Filter는 모든 요청에 대해 작동하는 필터이고,
Gateway Filter는 특정 라우트에만 적용되는 필터이다.
3 - 1. 필터 주요 객체
Mono
Mono는 리액티브 프로그래밍에서 0 또는 1개의 데이터를 비동기적으로 처리한다.
Spring Cloud Gateway에서 Mono가 필터 구현에 사용되는 이유는
1. 비동기 처리 :
Gateway는 높은 동시성을 처리해야 하므로, Mono를 사용하여 비동기적으로 논블로킹 방식으로 요청을 처리한다.
2. 리액티브 스트림 지원:
Mono는 리액티브 스트림을 지원하여 데이터를 시간에 따라 처리할 수 있게 해준다.
이는 Gateway에서 요청과 응답을 효율적으로 처리하는 데 도움이 된다.
3. 체이닝 가능:
Mono는 다양한 연산자를 제공하여 데이터 변환, 필터링, 에러 처리 등을 체인 형태로 구성할 수 있다.
4. 성능 최적화:
Mono를 사용하면 불필요한 스레드 전환을 줄이고, 리소스를 효율적으로 사용할 수 있다.
ServerWebExchange
ServerWebExchange는 HTTP 요청과 응답을 캡슐화한 객체이다.
exchange.getRequest()로 HTTP 요청을 가져온다.
exchange.getResponse()로 HTTP 응답을 가져온다.
GatewayFilterChain
GatewayFilterChain은 여러 필터를 체인처럼 연결한다.
chain.filter(exchange) 는 다음 필터로 요청을 전달한다.
3 - 2. 필터 시점별 종류
PreFilter
Pre 필터는 요청이 처리되기 전에 실행된다. 따라서 Pre 필터에서는 요청을 가로채고 필요한 작업을 수행한 다음, 체인의 다음 필터로 요청을 전달한다. 이때 추가적인 비동기 작업을 수행할 필요가 없기 때문에 then 메서드를 사용할 필요가 없다. Pre 필터는 주로 주로 로깅, 헤더 추가, 유효성 검사 등의 간단하고 빠른 동기 작업을 수행하여 요청을 신속하게 처리하고 다음 단계로 전달하는 역할을 한다.
예시
@Component
public class PreFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 요청 로깅
System.out.println("Request: " + exchange.getRequest().getPath());
return chain.filter(exchange);
}
@Override
public int getOrder() { // 필터의 순서를 지정합니다.
return -1; // 필터 순서를 가장 높은 우선 순위로 설정합니다.
}
}
PostFilter
Post 필터는 요청이 처리된 후, 응답이 반환되기 전에 실행된다. Post 필터에서는 체인의 다음 필터가 완료된 후에 실행되어야 하는 추가적인 작업을 수행해야 한다. 따라서, chain.filter(exchange)를 호출하여 다음 필터를 실행한 후, then 메서드를 사용하여 응답이 완료된 후에 실행할 작업을 정의 한다. 다시 말해, Post 필터에서는 비동기 작업이 있을 수 있다는 의미다.
예시
@Component
public class PostFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 응답 처리 후 실행될 비동기 작업!
// 응답 로깅
System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
}));
}
@Override
public int getOrder() {
return -1;
}
}
then() 메서드는 chain.filter(exchange)가 완료된 후에 실행될 추가 작업을 정의한다.
응답이 생성된 후, 클라이언트에게 전송되기 전에 수행할 작업을 지정하는데 사용된다.
4. 실습
클라우드 게이트웨이 + 유레카 + Order 인스턴스(1) + Product 인스턴스(2개)
4 - 1. 게이트웨이를 만들기 전 상황
4 - 2. 게이트웨이 만들기
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.5'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.spring-cloud.eureka.client'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2023.0.3")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
게이트웨이는 Reactive Gateway이다.
게이트웨이도 eureka-client 이다.
application.yml
server:
port: 19091 # 게이트웨이 서비스가 실행될 포트 번호
spring:
main:
web-application-type: reactive # Spring 애플리케이션이 리액티브 웹 애플리케이션으로 설정됨
application:
name: gateway-service # 애플리케이션 이름을 'gateway-service'로 설정
cloud:
gateway:
routes: # Spring Cloud Gateway의 라우팅 설정
- id: order-service # 라우트 식별자
uri: lb://order-service # 'order-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/order/** # /order/** 경로로 들어오는 요청을 이 라우트로 처리
- id: product-service # 라우트 식별자
uri: lb://product-service # 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/product/** # /product/** 경로로 들어오는 요청을 이 라우트로 처리
discovery:
locator:
enabled: true # 서비스 디스커버리를 통해 동적으로 라우트를 생성하도록 설정
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka 서버의 URL을 지정
predicates가 중요하다.
/order로 시작하는 요청은 order-service에 라우팅한다.
/product로 시작하는 요청은 product-service에 라우팅한다.
CustomPreFilter
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.logging.Logger;
@Component
public class CustomPreFilter implements GlobalFilter, Ordered {
private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest response = exchange.getRequest();
logger.info("Pre Filter: Request URI is " + response.getURI());
// Add any custom logic here
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
PreFilter는 간단한 로깅 작업만 수행하고 있다.
따라서, 비동기 작업이 필요하지 않다.
CustomPostFilter
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.logging.Logger;
@Component
public class CustomPostFilter implements GlobalFilter, Ordered {
private static final Logger logger = Logger.getLogger(CustomPostFilter.class.getName());
@Override
public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
logger.info("Post Filter: Response status code is " + response.getStatusCode());
// Add any custom logic here
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
PostFilter에서도 로깅 작업만 수행하고 있다.
단순한 로깅은 then()을 사용할 필요가 없지만, 주로 PostFilter에서 then()을 사용하므로 작성했다.
5. Run
게이트웨이에 /order를 호출
게이트웨이에 /product를 호출
게이트웨이에 /product를 한 번 더 호출해서 로드밸런싱이 되고 있는지 확인
References
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/global-filters.html
https://www.javainuse.com/spring/cloud-filter
https://lincoding.tistory.com/99
https://yarisong.tistory.com/46
'MSA' 카테고리의 다른 글
2024 11 29 TIL - 분산추적, Zipkin (2) | 2024.11.29 |
---|---|
2024 11 28 TIL - Spring Cloud Config (1) | 2024.11.28 |
2024 11 26 TIL - CircuitBreaker(Resilience4j) (0) | 2024.11.26 |
2024 11 25 TIL - 로드 밸런싱, Netflix Ribbon (0) | 2024.11.25 |
2024 11 22 TIL - Spring Cloud Netfilx Eureka로 알아보는 서비스 디스커버리 (3) | 2024.11.22 |