Gateway의 세 가지 역할 - LB, Token 검증, 로깅

shinny·2024년 7월 22일

[성능 개선]

목록 보기
8/8

🌟 Gateway의 세 가지 역할

  1. MSA 구조에서 특정 서버들이 다중 운영될 수 있다. 그 때 들어오는 요청에 대해 로드 밸런서 역할을 해줬으면 좋겠다.
  2. 토큰을 발급받은 사용자는 토큰 값을 헤더에 추가하여 요청을 보내게 되는데, 토큰의 유효성은 게이트 웨이에서 일괄적으로 검증을 해줬으면 좋겠다.
  3. 희망하는 경우에만, 특정 요청에 대해 로그를 남겨주었으면 좋겠다.

1️⃣ 로드밸런싱

서버가 새롭게 실행될 때, 서버 주소와 IP 값은 계속 변경된다. 그럴 때도 유레카 클라이언트를 등록해두면, 유레카 서버에서 새롭게 올라온 서버들도 자동으로 관리를 해준다. 그리고 게이트웨이에서는 서버의 수만큼 라운드로빈 방식으로 요청을 분배하여 전달한다.

1-1. 유레카 클라이언트 등록

implementation("org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:4.1.2")

spring-cloud-starter-netflix-eureka-client를 사용했다.
버전 : 4.1.2

1-2. 랜덤 포트

server:
  port: 0

1-3. 서비스 인스턴스 ID

eureka:
  instance:
    instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}

1-4. RouteConfig에서 URI 등록

게이트웨이 서버의 경우에도 유레카 서버가 관리할 수 있도록 등록해준다.

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

그런 다음, RouteConfig에서 유레카 서버에 등록된 각 서비스들의 애플리케이션 이름을 가져와서 uri 부분에 lb://{Application Name}으로 작성한다.

@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("coupon-api",
                        r -> r.path("/coupon/**")
                                .filters(f -> f.stripPrefix(1))
                                .uri("lb://COUPON-API"))
                .route("queue-for-reserve",
                        r -> r.path("/queue/**")
                                .filters(f -> f.stripPrefix(1))
                                .uri("lb://QUEUE-FOR-RESERVE"))
                .build();
    }
}

위와 같이 설정하면, 게이트웨이 서버에서 로드밸런싱을 위한 설정이 완료되었다. 서비스 인스턴스 N개가 UP일 때, 라운드로빈 방식으로 로드 밸런싱이 이루어진다.

2️⃣ Verify Tokens

토큰 검증의 경우에도 게이트웨이 서버에서 처리한다.
이렇게 되면 장점이 여러가지가 있다.
우선 첫 번째, 뒷 단 애플리케이션 서버에서는 처리해야 할 비즈니스 로직에만 집중할 수 있다.
두 번째, 토큰 검증의 처리 지점이 하나로 통일되어, 에러 발생 시 더 단순하게 원인을 파악할 수 있다.

작성 코드

  • TokenAuthenticationFilter
    GlobalFilter를 상속받아서, 모든 요청에 대해 토큰 검증 필터를 거치도록 구현하였다. 들어온 요청은 검증이 완료되었을 경우, 헤더에 이메일 값을 추가하여 전달하는 로직이다.
@Component
@Slf4j
public class TokenAuthenticationFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("AuthorizationHeaderFilter called. path : {}", exchange.getRequest().getPath().value());
        String jwt = extractJwt(exchange.getRequest());
        verifyJwt(jwt);
        String email = extractEmail(jwt);
        addHeader(exchange, email);
        log.info("AuthorizationHeaderFilter ending. path : {}, email : {}", exchange.getRequest().getPath().value(), email);
        return chain.filter(exchange);
    }
}

3️⃣ Logging

  • LoggingFilter
    LoggingFilter의 경우, AbstractGatewayFilterFactory를 상속받아 구현하였다. 그렇기 때문에 이 필터는 모든 요청에 대해 동작하는 것은 아니며, 특정 path 형태를 가진 요청에 대해서만 필터를 거치도록 RouteConfig에서 설정하고 커스텀하여 사용할 수 있다.
@Component
@Slf4j
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {

    @Override
    public GatewayFilter apply(Config config) {
        return (((exchange, chain) -> {
            var request = exchange.getRequest();
            var response = exchange.getResponse();

            log.info("Logging filter baseMessage. Application Name -> {}", config.getBaseMessage());

            if (config.preLogger) {
                log.info("Logging filter Start: request id -> {}", request.getId());
            }

            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                if (config.postLogger) {
                    log.info("Logging filter End: response code -> {}", response.getStatusCode());
                }
            }));
        }));
    }
}
profile
꾸준히, 성실하게, 탁월하게 매일 한다

0개의 댓글