프로그램/Java

Spring Boot Micro Service Architecture 만들기 (2)

with-RL 2023. 7. 9. 11:20

이 포스트는 이전 포스트에서 생성한 Spring Boot MSA 프로젝트를 기반으로 Load Balancing 테스트와 Log 기능을 만들어 보는 과정에 대한 설명입니다.

이 포스트는 다음 과정을 완료한 후에 참고하시길 바랍니다.

1. Gradle 빌드

  • Gradle을 이용해서 MSA 프로젝트를 빌드 하는 과정입니다. 우선 실행 중인 MSA 프로젝트를 모두 종료합니다.
  • IntelliJ 우측 툴바에서 ‘Gradle’을 누르고 뜨는 팝업창에서 ‘my-msa’ >> ‘Tasks’ >> ‘build’ >> ‘clean’을 선택하고 마우스 메뉴 버튼을 누른 후 뜨는 팝업메뉴에서 ‘Run my-msa [clean]’을 선택합니다.

  • 기존에 빌드 된 내용이 모두 지워집니다.

  • IntelliJ 우측 툴바에서 ‘Gradle’을 누르고 뜨는 팝업창에서 ‘my-msa’ >> ‘Tasks’ >> ‘build’ >> ‘build’을 선택하고 마우스 메뉴 버튼을 누른 후 뜨는 팝업메뉴에서 ‘Run my-msa [build]’을 선택합니다.

  • 모든 MSA 프로젝트가 빌드 됩니다. 빌드 결과는 아래 폴더에 있습니다. 그중에서도 build 폴더 내의 libs 폴더에 jar 파일이 최종 결과물입니다.
    app-core/build
    app-eureka/build
    app-gateway/build
    app-web/build

2. Load Balancing 테스트

  • 이번 과정은 Power Shell에서 프로젝트의 프로세스를 실행하고 Load Balancing 동작을 테스트해보는 과정입니다.
  • 우선 Power Shell을 5개 실행하고 각각 Power Shell에서 cd 명령을 이용해 my-msa 프로젝트 폴더로 이동합니다.

  • 첫 번째 Power Shell에서 아래 명령을 실행해서 app-eureka 서버를 실행합니다.
$ java "-Dspring.profiles.active=local" -jar .\app-eureka\build\libs\app-eureka-1.0.0.jar

  • 두 번째 Power Shell에서 아래 명령을 실행해서 app-gateway 서버를 실행합니다. 중간에 mongodb 관련 오류가 발생할 수 있지만 무시합니다.
$ java "-Dspring.profiles.active=local" -jar .\app-gateway\build\libs\app-gateway-1.0.0.jar

  • 세 번째, 네 번째, 다섯 번째 Power Shell에서 아래 명령을 실행해서 app-web 서버를 3개 실행합니다. 중간에 mongodb 관련 오류가 발생할 수 있지만 무시합니다.
$ java "-Dspring.profiles.active=local" -jar .\app-web\build\libs\app-web-1.0.0.jar

  • app-web을 여러 개 실행할 수 있는 이유는 ‘app-web/src/main/resources/application.yml’ 파일에 server의 port가 0으로 설정돼 있기 때문입니다. 0인 경우는 spring boot가 알아서 적당한 port로 app-web 서버를 실행합니다.

  • 브라우저에서 http://localhost:9000/에 접속합니다. 아래와 같이 1개의 APP-GATEWAY와 3개의 APP-WEB을 확인할 수 있습니다.

  • 브라우저에서 http://localhost:9001/app-web/hello에 접속합니다. ‘새로고침’ 버튼을 50번 정도 눌러서 동일한 요청을 계속 해 봅니다.
  • 3개의 app-web Power Shell에 ‘hello-request received’라는 Log가 출력되는 것을 확인할 수 있습니다. 간혹 2개의 Power Shell에만 Log가 출력되는 경우도 있는데 지속적으로 요청을 하다 보면 3개 모두 Log가 출력되는 것을 확인하실 수 있을 겁니다.

  • App-web의 ‘hello-request received’ Log는 ‘HelloController’에서 출력하도록 한 값입니다.

3. Log 기능 추가

  • 위의 로그 결과를 보면 약간의 문제점을 발견할 수 있습니다. 문제점은 아래와 같습니다.
    모든 입력은 app-gateway로 들어오는데 app-gateway에 로그가 생성되지 않습니다.
    여러 개의 web-app 중에서 어떤 app-web가 명령을 처리했는지 확인이 어렵습니다.
  • 위 문제를 해결하기 위해서 몇 가지 기능을 추가해 보겠습니다.

3.1. app-core에 MSAContext 클래스 생성

  • app-core 프로젝트에 ‘com.mymsa.core.context’ 패키지를 만들고 MSAContext 클래스를 생성한 후 아래와 같이 편집합니다.
package com.mymsa.core.context;

import org.apache.commons.lang.RandomStringUtils;

public class MSAContext {

    private final String sessionID;

    public MSAContext() {
        this.sessionID = RandomStringUtils.randomAlphabetic(8);
    }

    public MSAContext(String sessionID) {
        this.sessionID = sessionID != null ? sessionID : RandomStringUtils.randomAlphabetic(8);
    }

    @Override
    public String toString() {
        return sessionID;
    }

}

  • MSAContext 의 sessionID는 매 요청 당 생성되는 key 값으로 로그를 추적할 때 사용할 값 입니다.

3.2. app-gateway에 GlobalFilter 클래스 생성

  • 다음은 app-gateway 프로젝트에 ‘com.mymsa.gateway.filter’ 패키지를 생성하고 GlobalFilter 클래스를 생성한 후 아래와 같이 편집합니다.
package com.mymsa.gateway.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {

    public GlobalFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String sessionID = RandomStringUtils.randomAlphabetic(8);
            ServerHttpRequest request = exchange.getRequest().mutate()
                    .header("sessionID", sessionID)
                    .build();
            log.info("{}|gateway-begin|Y|src={},uri={},ms=", sessionID, exchange.getRequest().getRemoteAddress(), exchange.getRequest().getURI());
            return chain.filter(exchange.mutate().request(request).build())
                    .then(Mono.fromRunnable(() -> {
                        int code = exchange.getResponse().getStatusCode().value();
                        boolean result = code == 200;
                        log.info("{}|gateway-end|{}|code={}", sessionID, result ? "Y" : "N", code);
                    }));
        };
    }

    @Data
    public static class Config {
    }

}
  • app-gateway로 요청된 입력에 대해서 로그를 남기는 기능이 구현된 클래스 입니다.

  • 다음은 app-gateway의 src/main/resources/application.yml의 gateway 부분에 아래 내용을 추가합니다.
      default-filters:
        - name: GlobalFilter

3.3. app-web의 HelloController 클래스 수정

  • app-web의 HelloController 클래스이 내용을 아래와 같이 수정합니다.
package com.mymsa.web.controller;

import com.mymsa.core.context.MSAContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RequestMapping("/app-web")
@RestController
@Slf4j
public class HelloController {

    @GetMapping("/hello")
    public Mono<String> hello(@RequestHeader(value="sessionID", required=false) String sessionID) {
        MSAContext context = new MSAContext(sessionID);
        log.info("{}|hello-request||", context);
        return Mono.just("Hello: " + System.currentTimeMillis())
                .doOnSuccess(result -> {
                    log.error("{}|hello-request|Y|{}", context, result);
                })
                .doOnError(error -> {
                    log.error("{}|hello-request|N|", context, error);
                });
    }

}

  • log를 남길때 app-gateway에서 받은 sessionID를 함께 남기도록 수정 했습니다.

4. Load Balancing 테스트 (Log 기능 확인)

  • 이제 수정된 내용을 기반으로 Log 기능을 확인해 보겠습니다.
  • ‘2. Load Balancing 테스트’와 동일하게 우선 Power Shell을 5개 실행하고 각각 Power Shell에서 cd 명령을 이용해 my-msa 프로젝트 폴더로 이동합니다.
  • 첫 번째 Power Shell에서 아래 명령을 실행해서 app-eureka 서버를 실행합니다.
$ java "-Dspring.profiles.active=local" -jar .\app-eureka\build\libs\app-eureka-1.0.0.jar
  • 두 번째 Power Shell에서 아래 명령을 실행해서 app-gateway 서버를 실행합니다.
$ java "-Dspring.profiles.active=local" -jar .\app-gateway\build\libs\app-gateway-1.0.0.jar
  • 세 번째, 네 번째, 다섯 번째 Power Shell에서 아래 명령을 실행해서 app-web 서버를 3개 실행합니다.
$ java "-Dspring.profiles.active=local" -jar .\app-web\build\libs\app-web-1.0.0.jar
  • 브라우저에서 http://localhost:9001/app-web/hello에 접속합니다. ‘새로고침’ 버튼을 50번 정도 눌러서 동일한 요청을 계속 해 봅니다.
  • app-gateway에 로그가 출력 되는 것을 확인할 수 있습니다.

  • 첫 번째 app-web의 로그를 확인해 보면 app-gateway로 요청된 요청 중 ‘RVOQMheO’ 세션은 첫 번째 app-web로 전달된 것을 확인할 수 있습니다.

  • 두 번째 app-web의 로그를 확인해 보면 app-gateway로 요청된 요청 중 ‘PNYSDitG’ 세션은 두 번째 app-web로 전달된 것을 확인할 수 있습니다.

  • 세 번째 app-web의 로그를 확인해 보면 app-gateway로 요청된 요청 중 ‘ZoAyHCVl’ 세션은 세 번째 app-web로 전달된 것을 확인할 수 있습니다.

  • 이제 어제 어떤 요청이 어떤 app-web에 요청됐는지 알 수 있게 되었습니다.

참고

이 포스트와 관련 포스트 입니다.