🖥️ CS/Architecture

[마이크로서비스 아키텍처 구축] 4. MSA 마이크로서비스 통신 방식 - (1)

한국의 메타몽 2024. 10. 20. 17:47

book

해당 글은 마이크로서비스 아키텍처 구축에서 학습한 내용을 다룹니다.



목차

  1. 프로세스 내부에서 프로세스 사이로
    (1) 프로그램, 프로세스, 스레드의 관계
    (2) 왜 프로세스 간 호출의 오버헤드가 더 클까?
    (3) 인터페이스 변경
    (4) 에러 처리
  2. MSA 통신 방식
    (1) 요약
    (2) 비동기식 논블록킹과 관련된 예시


1. 프로세스 내부에서 프로세스 사이로


(1) 프로그램, 프로세스, 스레드의 관계

1. 프로그램이 구동되면 메모리에 올라간다.
2. 메모리에 올라간 프로그램의 상태를 프로세스라고 한다.
3. 이때 프로세스 내에서 실행되는 여러 흐름의 단위가 '스레드'이다.

Monolithic에선 프로세스 내부에서 호출이 이루어졌다면 MSA에선 프로세스 사이로 네트워크릍 통해 통신이 이루어짐

  • 프로세스 내부 호출이 일어날 때, 런타임은 호출의 영향을 줄이려고 마치 처음에 호출이 없었던 것처럼 호스트 최적화를 수행할 수 있음
  • 하지만 프로세스 간 호출에서는 이러한 최적화가 불가능하며, 패킷을 전송해야 함
  • 프로세스 내 호출의 오버헤드 < 프로세스 간 호출의 오버헤드
    • ex ) 데이터 센터 안에서 단일 패킷이 왕복하는데 걸리는 시간은 밀리초 단위로 측정 가능한 반면, 메서드 호출의 오버헤드는 걱정하지 않아도 될 만큼 작음

(2) 왜 프로세스 간 호출의 오버헤드가 더 클까?

  • 메서드에 매개변수를 전달할 때, 보통 전달할 데이터의 구조체는 이동하지 않음
    • 대신 메모리 위치에 대한 포인터를 전달
    • 때문에 데이터 복사가 이루어지진 않아 더 많은 메모리가 할당되진 않음
  • 반면 네트워크를 통한 MSA 호출이 이루질 때, 데이터는 실제 네트워크를 통해 전송될 수 있도록 직렬화가 이루어짐
    • 수신처에서는 반대로 역직렬화가 이루어짐

(3) 인터페이스 변경

  • Monolithic에서는 인터페이스가 변경될 경우 보통 사용중인 IDE를 통해 쉽게 유관 코드의 리펙터링이 가능
  • 그러나 MSA에서는 별개의 프로젝트로 나뉘므로, 호출처와 수신처의 인터페이스가 동시에 변경되는 것을 보장하려면 락스텝 배포 방식이 권장됨
    • 락스텝 배포 : 복수 개의 서비스를 동일한 CI/CD로 배포하는 것

(4) 에러 처리

  • 분산환경에서는 기존 Monolithic에서 예상 못한 에러들을 많이 마주함
    • ex ) 네트워크 시간 초과, 다운스트림 MSA가 일시적으로 사용 못하는 상황, 네트워크 연결이 끊김, 과도한 메모리 사용으로 컨테이너가 다운됨
  • 일반적으로 HTTP 프로토콜을 통해 MSA간의 호출이 이루어지는데, 보통 에러 코드의 의미는 하기와 같음
    • 400번대 : 본질적으로 요청 에러이며, 다운스트림(수신처)이 요청에 원래부터 문제가 있음을 클라이언트에게 알려줌
      • 이런 경우는 요청을 포기해야함. 404 Not Found에 재시도하는 것은 의미가 없음
    • 500번대 : 다운스트림 서버에 문제가 있으며, 일부 코드는 일시적인 문제라도 클라이언트에게 알려줌
      • ex ) 503 Service Unavailable은 일시적으로 다운스트림 서버가 요청을 처리할 수 없는 상태
  • 이러한 Monolithic에서 예상 못했던 에러처리를 위해 Circuit Breaker Pattern 적용 및 Resilience4j와 같은 라이브러리 사용이 필요됨


2. MSA 통신 방식


  • 동기식 블로킹 : MSA는 다른 MSA를 호출하고 응답을 기다리는 동안 다른 작업을 차단
  • 비동기식 논블로킹 : 호출을 보낸 MSA는 호출 수신 여부에 관계없이 처리를 계속할 수 있음
    • 요청 및 응답 : MSA는 작업을 수행하도록 다른 MSA에 요청을 보내며, 결과를 알려주는 응답을 받는 것을 기대
    • 이벤트 기반 : MSA는 다른 MSA가 소비하고 반응하는 이벤트를 발산. 이벤트를 발행하는 MSA는 자신이 발행하는 이벤트를 소비하는 MSA가 어떤 MSA인지 알지 못함
    • 공통 데이터 : 통신 방식으로 자주 볼 수 없는 MSA인 경우, 일부 공유 데이터 소스를 통해 협업

호출 방식 설명 장점 단점
동기식 호출 요청을 보내고 응답이 돌아올 때까지 기다리며 차단되는 방식 - 코드 흐름이 직관적이고 단순함 - 응답을 기다리는 동안 호출한 서비스가 차단됨
- 요청-응답 방식이 명확하여 처리 상태를 쉽게 파악할 수 있음 - 응답 지연이나 네트워크 문제로 성능 저하 발생 가능
- 비동기식으로 호출할 때 보다 다운스트림 장애로 인한 연쇄적인 문제에 더욱 취약
비동기식 호출 요청을 보내고 응답을 기다리지 않으며, 비동기 콜백이나 이벤트로 처리 - 호출한 서비스가 응답을 기다리지 않고 다른 작업을 계속 수행할 수 있음 - 코드 복잡도가 증가할 수 있음 (콜백, Future, 이벤트 기반 처리)
- 네트워크 지연에도 시스템 자원을 효율적으로 사용 가능 - 요청의 결과를 즉시 확인하기 어려울 수 있음
- 높은 처리량 및 빠른 응답성을 제공할 수 있음 - 복잡한 에러 처리 및 예외 관리가 필요할 수 있음
공통 데이터를 통한 통신 - 하나의 MSA가 데이터를 정의한 위치에 넣고, 다른 MSA가 그 데이터를 이용할 때 사용

- 데이터를 저장하기 위한 영구 저장소 필요

- 대용량 데이터를 저장하기 위해 data lake또는 data warehouse가 필요되며, 다운스트림에서는 새 데이터가 가용하다는 사실을 인식하기 위해 일반적으로 폴링(polling) 매커니즘을 사용
- 간단하게 구현 가능 (DB 사용) - 폴링(polling) 매커니즘이 주기적으로 트리거되는 정기 작업을 통해 처리할 신규 데이터가 있음을 인식하는데 사용되며, 대기 시간이 짧은 상황에서는 유용하지 못함

- 대용량 데이터를 실시간으로 처리해야한다면 kafka같은 스트리밍 기술이 적합
- 사용 가능한 기술에 제약이 있는 프로세스에서 메리트가 있음
- gRPC Kafka Redis같은 기술을 사용하지 못하는 구형 시스템의 경우, 대안책이 될 수 있음

(2) 비동기식 논블록킹과 관련된 예시

  • 환율과 같은 정보는 하루 종일 수시로 변동되며, 일반적으로 메시지 브로커를 통해 환율 정보를 받음
async function f() {
    let eurToGbp = new Promise((resolve, reject) => {
        // 최신 EUR 대 GBP 환율을 가져오는 코드
        ...
    });
    var latestRate = await eurToGbp;
    process(latestRate);
}
/*
1. 최신 EUR 대 GBP 환율을 가져올 때까지 대기
2. Promise가 수행 완료될때까지 실행되지 않음
*/

  • await을 사용해 eurToGbp를 참조할 때는 latestRate의 상태가 채워질 때까지 블로킹됨
  • processeurToGbp 상태를 확인할 때까지 완료되지 않음
  • 환율을 비동기적으로 받더라도 이 문맥에서 awiat을 사용한다는 것은, latestRate의 상태가 해결될 때까지 블로킹 된다는 의미
  • 따라서 환율을 가져오는 데 사용하는 하부 기술을 사실상 비동기식으로 간주할 수 있더라도 (ex : 환율 대기), 코드 관점에서는 본질적으로 동기식 블로킹 상호작용