then 메서드를 사용할 필요가 없습니다.chain.filter(exchange)를 호출하여 다음 필터를 실행한 후, then 메서드를 사용하여 응답이 완료된 후에 실행할 작업을 정의합니다.
cloud gateway + eureka + order instance (1) + product instance (2)
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을 지정
@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 request = exchange.getRequest();
logger.info("PreFilter : Request URI : " + request.getURI());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 최상위 순서
}
}
요청을 처리 하기 전에 실행되는 필터로 최상위 순서를 가지고 있다.
GlobalFilter 를 사용함으로써 모든 요청에 대한 로깅을 하는 필터라 생각할 수 있다.
@Component
public class CustomPostFilter implements GlobalFilter, Ordered {
private static final Logger logger = Logger.getLogger(CustomPostFilter.class.getName());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
logger.info("PostFilter : Response Status Code : " + response.getStatusCode());
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
모든 요청에 대해서 응답이 나갈 때 로깅을 하는 역할을 한다.
특별하게 볼 점은 chain.filter 뒤에 then(Mono.fromRunnable(...)) 메서드를 통해 이전의 필터 체인이 완료된 후 실행할 작업을 정의한다. 여기는 Response에 대한 상태코드를 호출중이다.
최 하위 순서로 설정해 결과가 반환되기 가장 마지막에 실행됨을 유추할 수 있다.
게이트웨이를 활용할 수 있는 방법은 매우 다양하다고 생각한다.
토스 개발블로그에서 사용처는 크게
으로 사용한다고 한다.
그리고 또 중요한 점 중 하나는 모니터링 이다.
모니터링에 중요한 요소로는 로깅, 메트릭, 트레이싱이 있는데 토스에서는 로깅은 Elasticsearch로 적재하고, 메트릭은 Prometheus & Grafana 으로 수집 & 모니터링을 수행한다고 한다.
또한 게이트웨이는 단일 진입점으로 SPOF 문제가 발생할 수 있다.
토스에서는 Gateway는 Stateless 하게 개발하여 K8S 위에 여러개의 pod 을 두고 트래픽을 분산하고, 또한 업데이트 배포가 있을때는 카나리 배포를 통해 소수점 단위로 최소한 트래픽을 태워 긴시간 검증하고 배포하고 있으며, 카나리 배포를 할 수 없는 경우에도 Active - Active 구조로 된 데이터 센터의 트래픽을 조정하여 데이터 센터 간 카나리 배포를 하고 있습니다.
라고한다.
역시 스케일 아웃을 생각해서 서비스를 개발하는 단계에서도 Stateless 하게 개발해야 한다.
서버의 배포 또한 긴 시간 세세하게 검증하는 것 같고, Active - Active 구조의 데이터 센터까지 있다고 하니 상상할 수 없게 거대한 것 같다.