[내일배움캠프 과제] MSA 강의 정리

2026. 2. 23. 16:33·내배캠

MSA 정리

1-1 오리엔테이션

MSA의 기능별로 어떤 역할을 하는지 체크해야 한다.
이에 따라 MSA가 다른 환경에서 개발하더라도 해당 역할/기능들을 구현해 나가면 된다.

1-2 MSA

모놀리틱 구조에서 배포의 문제
특정 기능(서비스)을 수정하면 다른 기능에 영향이 갈 수도 있고,
이후 배포 시 다른 기능들도 같이 업데이트가 된다.

 

배포에 소요되는 시간 동안 문제가 생길 수 있다.

이때 어느 곳에서 문제가 생긴 지 알 수 없다.

 

MSA에서는 서비스를 나눠서 독립적으로 관리하기에
각각 따로 배포할 수 있어 다른 서비스에 영향을 미치지 않는다.

하지만 그만큼 각각의 서비스를 관리하는데 발생하는 비용이 증가하고
각 서비스를 연결/호출하는데 비용이 발생한다.

 

1-3 Spring Cloud

MSA를 구축하는데 필요한 스프링 프레임워크의 확장

 

- 서비스 등록 및 디스커버리

  1. Eureka : 각 서비스의 위치를 동적으로 관리한다.
  2. Ribbon : 라우팅시 서비스의 부하를 낮추기 위해 로드밸런싱을 제공한다.
    사용되는 로드밸런싱 알고리즘인 라운드로빈은 각 서비스를 하나씩 돌아가면서 호출한다.

- 서킷 브레이커

  1. Hystrix : 서비스 간의 호출 실패를 감지 ( 레거시 )
  2. Resilience4j ( R4j ) : Hystrix의 대안으로 3개의 상태(클로즈드 -> 오픈 -> 하프-오픈)로 나눠 호출 실패 시 로직을 처리한다.

-  API 게이트웨이
1. Zuul ( 레거시 )
2. Cloud Gateway

서비스가 나눠짐에 따라 클라이언트 입장에서 일관된 요청 처리를 위해
각 서비스 앞단에서 클라이언트의 요청을 처리하고 각 서비스로 라우팅 한다.

이때, 클라이언트의 요청 전 후에 필터작업이 가능하여 로그인처리 및 HTTP 가공을 할 수 있다.

 

- 구성 관리

Spring Cloud Config로 Config Server와 Client로 나누고
Config Server에서 각 클라이언트들의 설정 파일을 중앙에서 관리한다.

- Neflix
모놀리틱에서 MSA로 전환한 이유는 확장성 때문이었다.

MSA + CI/CD 파이프라인 + MSA에 필요한 도구를 자체적으로 개발 + AWS로 서비스를 구축했다.

 

스프링 참고 사이트 https://spring.io/projects/spring-cloud#learn

 

Spring Boot

 

spring.io

- GA : 정식 릴리즈 버전

- SNAPSHOT : 개발 중인 버전
- 3.3.1 => 주버전.마이너버전.패치버전

 

전체적인 틀)

MSA의 전체적인 흐름

- 각 서비스의 위치는 Eureka Server가 관리한다.
게이트웨이에서 호출 시 Eureka Server에서 레지스트리를 받아 라우팅 한다.

- 각 서비스의 설정 파일은 Config Server가 관리한다.
각각의 DB 정보, 시크릿 키 등의 환경정보를 관리할 수 있다.

1-4 서비스 디스커버리

Eureka Server / Eureka Client는 각자 yml 파일에 설정을 해야 한다.
서버와 클라이언트 둘 다 자신의 포트번호와 호스트명을 설정한다.


서버에서는 다른 Eureka 서버에 등록/가져오기를 하지 않는 설정
클라이언트가 사용할 서버 URL을 등록한다.
그리고 앱에서 @EnableEurekaServer를 붙인다.

 

클라이언트는 Eureka 서버설정을 가져오고
서버에 등록/가져오기 설정을 한다.
클라이언트는 앱에 별도의 어노테이션을 붙이지 않아도 동작한다.


RestTemplate과 FeignClient 둘 다 다른 서비스를 호출하는 용도이다.
호출 방식이 명령형이냐 선언형이냐의 차이가 있다.


- RestTemplate 사용

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

먼저 @Bean 수동 등록을 함과 동시에
@LoadBalanced로 로드밸런싱 설정을 한다.

@GetMapping("/get-data-rest")
    public String getDataWithRestTemplate() {
        String serviceUrl = "http://my-service/api/data";
        return restTemplate.getForObject(serviceUrl, String.class);
    }

컨트롤러에서 해당 서비스의 호스트명을 사용한다.
Eureka 서버에서 호스트명으로 해당 서비스를 찾아내서 요청이 보내지게 된다.

 

- FeignClient 사용
1. 사용할 서비스앱에서 @EnableFeignClients를 붙인다.

2. @FeignClient를 직접 생성한 인터페이스에 붙이고
옵션으로 name ="유레카에 등록된 서비스 이름"을 넣는다.


그리고 가져올 서비스의 컨트롤러를 참고해서 메서드를 정의한다.

이때 메서드명은 달라도 되지만 파라미터나 HTTP 요청/엔드포인트는 동일하게 설정해야 한다.

@FeignClient(name = "my-service")
public interface MyServiceClient {

    @GetMapping("/api/data")
    String getData();
}

 

3. 2에서 만든 인터페이스의 메서드를 필요한 곳에서 호출한다.

@RestController
public class MyFeignClientController {

    @Autowired
    private MyServiceClient myServiceClient;

    @GetMapping("/get-data-feign")
    public String getDataWithFeignClient() {
        return myServiceClient.getData();
    }
}

 
- 의존성 ( 자바 3.5.11 기준 )

 dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
}

1-5 로드밸런싱

로드밸런싱 : 클라이언트 사이드 로드밸런싱 / 서버 사이드 로드 밸런싱

클라이언트 사이드 로드 밸런싱 : 클라이언트가 직접 여러 서버 중 하나를 선택해서 요청을 보내는 방식
클라이언트는 서버의 목록을 가지고 있다 => 이를 바탕으로 로드밸런싱 수행

 

FeignClient

Eureka와 통합하여 서비스 인스턴스의 목록을 동적으로 조회, 로드밸런싱 수행 => 클라이언트 사이드 로드 밸런싱 중 하나

Ribbon이 통합되어 있어 자동으로 로드 밸런싱을 수행한다.

앱에서 @EnableFeignClients를 붙여야 동작한다.


자동 로드 밸런싱을 활성화하기 위해 yml에 아래 설정을 추가한다.

my-service:
  ribbon:
    eureka:
      enabled: true

1-6 서킷브레이커

R4j : 서킷브레이커 라이브러리, 서비스 간 호출 실패 감지, 장애 격리 => 복원력 높임

서킷브레이커는 3개의 상태로 호출 실패를 관리한다.

 

1. 클로즈드(Closed)

정상적인 상태
실패율이 임계점을 넘기면 오픈 상태로 전환

2. 오픈(Open)

오픈상태로 전환 시 모든 요청을 실패로 처리한다.
이 상태에서 요청이 들어오면 바로 에러 응답을 반환한다.

설정된 대기시간 후 하프-오픈으로 전환

 

3. 하프-오픈(Half-Open)

제한된 수의 요청을 허용하여 정상 동작하는지 체크
-> 제한된 수의 요청이 모두 성공한다면 클로즈드로 전환
-> 하나라도 실패한다면 다시 오픈상태로 전환

호출 실패 시 대체 로직을 제공하는 Fall back

서킷브레이커의 상태를 모니터링하는 도구 제공

R4j는 깃허브의 구현체가 있는 버전을 사용한다.
스프링 이니셜라이저로 추가한다면 구현체가 포함되지 않는다.


- 의존성

implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'

 

- R4j의 yml 설정

resilience4j:
  circuitbreaker:
    configs:
      default:  # 기본 구성 이름
        registerHealthIndicator: true  # 애플리케이션의 헬스 체크에 서킷 브레이커 상태를 추가하여 모니터링 가능
        # 서킷 브레이커가 동작할 때 사용할 슬라이딩 윈도우의 타입을 설정
        # COUNT_BASED: 마지막 N번의 호출 결과를 기반으로 상태를 결정
        # TIME_BASED: 마지막 N초 동안의 호출 결과를 기반으로 상태를 결정
        slidingWindowType: COUNT_BASED  # 슬라이딩 윈도우의 타입을 호출 수 기반(COUNT_BASED)으로 설정
        # 슬라이딩 윈도우의 크기를 설정
        # COUNT_BASED일 경우: 최근 N번의 호출을 저장
        # TIME_BASED일 경우: 최근 N초 동안의 호출을 저장
        slidingWindowSize: 5  # 슬라이딩 윈도우의 크기를 5번의 호출로 설정
        minimumNumberOfCalls: 5  # 서킷 브레이커가 동작하기 위해 필요한 최소한의 호출 수를 5로 설정
        slowCallRateThreshold: 100  # 느린 호출의 비율이 이 임계값(100%)을 초과하면 서킷 브레이커가 동작
        slowCallDurationThreshold: 60000  # 느린 호출의 기준 시간(밀리초)으로, 60초 이상 걸리면 느린 호출로 간주
        failureRateThreshold: 50  # 실패율이 이 임계값(50%)을 초과하면 서킷 브레이커가 동작
        permittedNumberOfCallsInHalfOpenState: 3  # 서킷 브레이커가 Half-open 상태에서 허용하는 최대 호출 수를 3으로 설정
        # 서킷 브레이커가 Open 상태에서 Half-open 상태로 전환되기 전에 기다리는 시간
        waitDurationInOpenState: 20s  # Open 상태에서 Half-open 상태로 전환되기 전에 대기하는 시간을 20초로 설정

 

서킷브레이커의 인스턴스가 생겼을 때 위 default 설정으로 생성된다.

 

- Fallback 사용법

@Service
public class MyService {

    @CircuitBreaker(name="서킷브레이커 서비스명", fallback="호출실패시 사용할 메서드명")
    public String myMethod(){
        // 외부 서비스 호출
        return 외부서비스.메서드(); // 꼭 외부서비스 호출 메서드일 필요는 없다.
    }

    public String 호출실패시사용할메서드명(){
        return "호출 실패";
    }
}

즉, 위에 name에 적은 서비스명으로 서킷브레이커의 인스턴스가 생성되고
yml에서 설정한 대로 default 설정을 가진다.

Fallback으로 장애가 전파되는 것을 방지한다.

- R4j Dashboard 의존성

dependencies {
    implementation 'io.github.resilience4j:resilience4j-micrometer'
    implementation 'io.micrometer:micrometer-registry-prometheus'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

Prometheus로 수집된 메트릭을
Grafana 대시보드로 시각화


체크경로)
http://${hostname}:${port}/actuator/prometheus

 

- Eureka를 활용

spring:
  application:
    name: my-service
  cloud:
    circuitbreaker:
      resilience4j:
        enabled: true

1-7 API GW

클라이언트의 요청을 백앤드 서비스로 라우팅 + 보안/로깅/모니터링/필터링

 

- 의존성

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
	  implementation 'org.springframework.boot:spring-boot-starter-actuator'
	  implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	  implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}

 

필터)
Global Filter -> 클라이언트의 모든 요청에 대해 작동
Gateway Filter -> 특정 라우트에만 작동

 

Mono => 0 or 1개의 데이터를 비동기로 처리

동기 vs 비동기
동기 => 순차적으로 처리
비동기 => 동시에 시작, 다음 동작의 순서를 제어할 필요가 있음

 

ServerWebExchange => HTTP 요청과 응답을 캡슐화한 객체 

- exchange.getRequest()
- exchange.getResponse()

 

GatewayFilterChain
chain.filter(exchange)

 

Pre 필터 => 요청이 처리되기 전에 실행 ( then 사용 x, 즉 처리하고 return )
Post 필터 => 요청이 처리되고 난 후 실행 ( then 사용, 즉 return문에서 사용 )

 

- 게이트웨이의 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을 지정

- 다른 서비스처럼 기본적인 Eureka Client 설정
- reactive 설정
- 라우팅, 로드밸런싱 설정

1-8 보안구성

JWT는 값이 변조되었는지 아닌 지를 체크한다.
즉, 값이 그대로 왔는지 아닌 지

 

로그인 유무, 즉 JWT를 소지하고 있는지 아닌 지를 게이트웨이에서 체크할 수 있다.

Auth 서비스에서는 Spring Security로 로그인 시 JWT를 발급, 회원 가입 시 DB에 저장한다.
게이트웨이에서는 유저가 어떤 요청을 했을 때 JWT를 가지고 있는지 체크한다.


- JWT 의존성

implementation 'io.jsonwebtoken:jjwt:0.12.6'


Auth 앱에서는 기존 Eureka Client 설정과 더불어
JWT 시크릿키와 만료시간 등의 JWT 관련 설정도 추가한다.

 

- Auth의 yml 설정 일부

service:
  jwt:
    access-expiration: 3600000
    secret-key: "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"


게이트웨이에서 Pre 필터를 만들어서
로그인이나 회원가입 요청은 필터를 바로 통과하게 설정한다.


마찬가지로 Auth에서도 SecurityChainFilter에서 로그인/회원가입 등의 요청은 인증하지 않는 설정을 추가한다.

1-9 Config

각 서비스들의 설정 파일을 중앙에서 관리, 변경 사항을 앱의 재배포 없이 실시간으로 반영할 수 있다.

 

- Config 서버 의존성

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-config-server'
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

추가로 앱에서 @EnableConfigServer을 붙인다.

 

- 클라이언트 의존성

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
}

 

- 클라이언트 yml 설정

spring:
  application:
    name: my-config-client
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
     
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

기본 Eureka 클라이언트 설정에
컨피그 서버를 등록하는 설정을 추가한다.

 

컨피그 서버에서는 서비스명-프로필.yml을 생성/관리해 각 서비스들의 환경에 맞게 제공할 수 있다.


변경된 설정을 수동 반영하는 법)
- Config Client의 yml 파일을 수정

management:
  endpoints:
    web:
      exposure:
        include: refresh

위 부분을 추가한다.


- 각 서비스에서 /actuator/refresh로 POST 요청을 호출한다.

네이티브 모드 => 설정 변경을 위해 재시작을 해야 한다.

1-10 분산추적

각 서비스의 호출 관계와 성능을 시각화하여 지연시간을 확인할 수 있다.


Micrometer
서킷브레이커와 마찬가지로 대시보드를 제공한다.

 

Zipkin
각 서비스의 트레이스와 스팬 데이터를 저장 + 호출 흐름을 시각화
검색 및 필터링(트레이스와 스팬)
+ 도커로 실행하는 것이 제일 간편

docker run -d -p 9411:9411 openzipkin/zipkin

 

- Zipkin yml 설정 추가

management:
  zipkin:
    tracing:
      endpoint: "http://localhost:9411/api/v2/spans"
  tracing:
    sampling:
      probability: 1.0

 

- 의존성

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'io.micrometer:micrometer-tracing-bridge-brave'
	implementation 'io.github.openfeign:feign-micrometer'
	implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
}

1-11 이벤트 드리븐

1. 이벤트들을 모아서 이벤트 소스를 만든다.
- 배송지 입력

- 결제 처리

- 상품 처리
=> 이벤트 소스

 

2. 이벤트 버스에서 각 서비스로 이벤트를 전달한다. ( Message Queue )
ex) Kafka, RabbitMQ

3. 이벤트핸들러에서 각 이벤트를 수신하여 처리한다.

- 재고 서비스
- 배송 서비스
- 결제 서비스

 

3개의 이벤트 중 2개는 성공하고 1개만 실패했을 때,
성공한 2개도 취소되어야 한다면?
다시 취소 요청을 보내야 한다.

이때 에러를 담당하는 서비스가 별도로 존재할 수도 있다.
이 경우엔 에러서비스에서 모아서 다시 메인 큐(이벤트 버스)로 취소 요청을 보낸다.

이후 다시 이벤트 핸들러에서 처리

이벤트를 생성하는 주체를 Producer
이벤트를 처리하는 주체를 Consumer라고 한다.

1-12 마무리

쿠버네티스는 애플리케이션 배포 자동화 및 운영 플랫폼이다.

MSA가 애플리케이션 레벨에 초점을 뒀다면
쿠버네티스는 인프라 레벨에 초점을 뒀다.

실제 기업에서는 쿠버네티스를 커스텀해서 UI로 활용하기도 한다.

 

Gateway에서 JWT에 대한 검증을 진행한다.
JWT 검증 시 role정보를 요청 헤더에 키를 x-user-id, x-role로, 값을 JWT에 넣을 clain값으로 설정하고
각 앱에서 @RequestHeader 통해서 가져올 수 있다.
이후 가져온 권한에 따라서 CRUD 같은 기능에 접근을 제한할 수 있다.

 

삭제는 실제로 데이터 한 행을 삭제할 수도 있지만
엔티티에 deletedAt 같은 필드를 만들고
삭제가 필요할 때 deleteAt에 현재 시간을 기록하고
조회 시 deletedAt 칼럼의 값이 null인 값만 조회할 수 있다.
이는 삭제된 데이터를 확인/복구하는데 도움이 된다.

 

메시지큐(이벤트 버스)를 도입해서 요청을 처리할 수 있다.

 

API시각화는 로그인/회원가입과 각 서비스의 CRUD + Search들을 시각화하여 나타내어 개발범위를 명확히 할 수 있다.

 

QueryDSL로 검색 기능에서 조건검색을 여러 개로 나눠 만들지 않고
하나의 메서드로 처리할 수 있다.
- JPA의 경우에는 쿼리메서드로 각각 정의/사용해야 한다.

 

빌더 패턴 사용 시 액세스 레벨을 설정하여 외부에서 생성하지 못하게 제한할 수 있다.

Enum사용으로 입력값을 제한할 수 있다.

'내배캠' 카테고리의 다른 글

[내일배움캠프 본캠프] 15일 차 TIL 1차 팀 프로젝트 시작  (0) 2026.02.24
[내일배움캠프 본캠프] 14일 차 TIL Layered Architecture  (0) 2026.02.23
[내일배움캠프 본캠프] 13일 차 TIL 상품-주문 MSA 프로젝트  (0) 2026.02.22
[내일배움캠프 본캠프] 12일 차 TIL MSA  (0) 2026.02.21
[내일배움캠프 본캠프] 11일 차 TIL MSA, Git flow  (0) 2026.02.20
'내배캠' 카테고리의 다른 글
  • [내일배움캠프 본캠프] 15일 차 TIL 1차 팀 프로젝트 시작
  • [내일배움캠프 본캠프] 14일 차 TIL Layered Architecture
  • [내일배움캠프 본캠프] 13일 차 TIL 상품-주문 MSA 프로젝트
  • [내일배움캠프 본캠프] 12일 차 TIL MSA
MvA
MvA
백엔드 개발자 김재현입니다. 주로 공부하면서 느낀점을 기록합니다.
  • MvA
    Man vs Ai
    MvA
  • 전체
    오늘
    어제
    • 분류 전체보기 (94)
      • Java (6)
      • Python (8)
        • 딥러닝 (1)
        • 머신러닝 (7)
      • JavaScript (2)
      • 내배캠 (60)
      • 개인 프로젝트 (11)
      • 책 후기 (5)
      • 기타 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    배포
    TiL
    머신러닝
    내일배움캠프
    딥러닝
    아키텍처
    Riot API
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
MvA
[내일배움캠프 과제] MSA 강의 정리
상단으로

티스토리툴바