[MSA] 03. 프로세스 간 통신

Jimin Lim·2023년 12월 10일
0

Architecture

목록 보기
14/23

3.1 마이크로서비스 아키텍처 IPC 개요

  • IPC: inter process communication

상호 작용 스타일

클라이언트/서비스 상호 작용 스타일은 (1)일대일/일대다 여부, (2)동기/비동기 여부 로 분류할 수 있다.

일대일/일대다

  • 일대일: 각 클라이언트 요청은 한 서비스가 처리
    • 요청/응답, 비동기 요청/응답, 단방향 알림(클라이언트가 서비스에 일방적인 요청만 하고 서비스는 응답없음)
  • 일대다: 각 클라이언트 요청은 여러 서비스가 협동
    • 발행/구독: 클라이언트가 알림 메시지 발행하고, 관심있는 0개 이상의 서비스가 메시지 소비
    • 발행/비동기 응답: 클라이언트는 요청 메시지 발행, 주어진 시간 동안 관련 서비스가 응답하길 기다림

동기/비동기

  • 동기: 클라이언트가 서비스가 제시간에 응답할 것이라 기대하고 대기 도중 블로킹 가능
  • 비동기: 응답이 즉시 전송되지 않아도 됨

API 발전시키기

시맨틱 버저닝

Major, Minor, Patch 세 파트로 구성하고 각각 증가 시킴

  • REST API: 경로의 첫 번째 엘리먼트
  • 메시징 기반 서비스: 서비스가 발행한 메시지에 버전 번호

하위 호환되는 소규모 변경

  • 옵션 속성을 요청에 추가
  • 속성을 응답에 추가
  • 새 작업 추가

-> 이런 변경은 기존 클라이언트는 별 문제없이 동작한다.

중대한 대규모 변경

기존 버전과 호환 안되는 변경을 API에 적용해야할 때, URL에 메이저 버전 번호 삽입한다. (/v1, /v2)

혹은 NIME 타입 내부에 버전 번호를 끼워 넣는다.

메시지 포맷

텍스트 메시지 포맷

  • JSON, XML 등, 사람이 읽을 수 있고 필요한 값만 골라 쓸 수 있다.
  • 다소 길고 파싱하는데에 오버헤드가 존재할 수 있다.

이진 메시지 포맷

  • 프로토콜 버퍼, 아브로 등, 컴파일러가 메시지를 직렬화/역직렬화하는 코드를 생성한다.

3.2 동기 PRI 패턴 응용 통신

  • RPI(Remote Procedure Invocation): 클라이언트가 서비스에 요청을 보내면 서비스가 처리 후 응답을 회신하는 IPC
    • 메시징으로 통신하는 클라이언트와 달리 응답이 제때 도착하리라 가정

동기 RPI 패턴: REST

  • HTTP로 소통하는 IPC!

REST 성숙도 모델

  • 레벨0: 유일한 URL 끝점에 HTTP POST 요청을 해서 서비스 호출, 요청을 할 때마다 어떤 액션을 수행할지, 그 대상은 무엇인지 지정 -> 리소스 개념은 지원안하는듯?
  • 레벨1: 리소스 개념은 지원하는데 수행할 액션은 요청
  • 레벨2: HTTP 동사 이용해서 액션 수행
  • 레벨3: 서비스를 HATEOAS 원칙에 기반하여 설계
    • ex) GET 요청으로 주문 데이터 조회, 반환된 표현형 내부 링크를 이용해 해당 주문 취소

REST의 단점

  • 요청 한 번으로 많은 리소스를 가져오기 어렵다

    • GraphQL이나 팔코 등 대체 API 기술 각광받기 시작
  • 작업을 http 동사에 매핑하기 어렵다

    • 수정할 때 PUT 동사를 주로하는데, 업데이트하는 경로는 주문취소/변경 등 다양하다.
    • 이로 인해 /orders/{orderId}/cancel, /orders/{orderId}/revise를 두는 방법도 있지만 REST 답지 않아 gRPC가 인기를 끈다.
  • 클라/서비스가 직접 통신해서 교환이 일어나는 동안 양쪽 다 실행 중이어야 해서 가용성이 떨어진다.

동기 RPI 패턴: gRPC

  • HTTP는 한정된 동사만 지원하기에 다양한 업데이트 작업을 지원하는 REST API 를 설계하기 쉽지않다. 따라서 gRPC가 등장하게 되었다.
  • 이진 메시지 기반의 프로토콜

장점

  • 다양한 업데이트 작업이 포함된 API 설계 쉬움
  • 큰 메시지를 교환할 때 콤팩트하고 효율적
  • 양방향 스트리밍 덕에 RPI, 메시징 두 가지 통신 방식 모두 가능

단점

  • JS 클라이언트가 하는 일 많아짐
  • 구형 방화벽은 HTTP/2를 지원하지 않음

부분 실패 처리: 회로 차단기 패턴

  • 동기 호출할 때 부분 실패할 가능성이 있다. 클라이언트는 응답 대기 도중 블로킹 되기에 서비스 실패는 전체 시스템의 중단을 초래할 위험이 있다.

견고한 RPI 프록시 설계

  • 네트워크 타임아웃: 항상 타임아웃을 걸어 리소스가 마냥 붙잡히지 않도록
  • 미처리 요청 개수 제한: 특정 서비스에 미처리 요청의 최대 개수를 설정한 후, 이 개수에 이르면 더 이상의 요청은 무의미하므로 즉시 실패 처리하도록
  • 회로 차단기 패턴: 성공/실패 요청 개수를 지켜보다가 에러율이 주어진 임계치를 초과하면 기 이후 시도는 바로 실패 처리

넷플릭스 히스트릭스 를 고려해보자

불능 서비스 복구

  • 그냥 알기 쉽게 에러 반환
  • 부분 실패 시 미리 정해진 기본 값이나 캐시된 응답 된 대체 값 반환

서비스 디스커버리

REST API가 있는 서비스를 호출하려면 서비스 인스턴스의 네트워크 위치를 알고 있어야 한다. 하지만 요즘 클라우드 기반의 마이크로서비스 애플리케이션은 네트워크 위치가 동적이라 식별하는 것이 쉽지 않다. 따라서 서비스 디스커버리를 사용할 수 밖에 없다.

핵심은 애플리케이션 서비스 인스턴스의 네트워크 위치를 DB화한 서비스 레지스트리이다.

애플리케이션 수준의 서비스 디스커버리 패턴 적용

  • 애플리케이션 클라이언트/서비스가 서비스 레지스트리와 직접 통신하는 방법
  • 서비스 인스턴스는 (1) 자신의 네트워크 위치를 서비스 레지스트리에 등록하고, (2) 서비스 클라이언트는 이 레지스트리로부터 전체 서비스 인스턴스 목록을 가져와 그중 한 인스턴스로 요청을 라우팅
    • (1) 자가 등록
    • (2) 디스커버리 패턴

스프링 클라우드 기반의 서비스는 유레카에 자동 등록되며, 스프링 클라우드 기반의 클라이언트는 유레카를 기본 서비스 디스커버리로 사용한다.

플랫폼에 내장된 서비스 디스커버리 패턴 적용

도커, 쿠버네티스 등 최신 배포 플랫폼에 대부분 서비스 레지스트리, 서비스 디스커버리 메커니즘이 탑재되어있다.

  • 배포 플랫폼은 DNS명, 가상 IP주소, VIP 주소로 해석되는 DNS명을 각 서비스마다 부여
  • 서비스 클라이언트가 DNS명/VIP 를 요청하면 배포 플랫폼이 알아서 가용 서비스 인스턴스 중 하나로 요청 라우팅

즉, 배포 플랫폼이 서비스 등록, 서비스 디스커버리, 요청 라우팅 전부 관장

3.3 비동기 메시징 패턴 응용 통신

메시징은 서비스가 메시지를 서로 비동기적으로 주고받는 통신 방식

메시징 개요

메시지

  • 커맨드: RPC 요청과 동등한 메시지, 호출할 작업과 전달할 매개변수 지정
  • 이벤트: 송신자에게 어떤 사건이 발생했음을 알리는 메시지, 대부분 Order, Customer 같은 도메인 객체의 상태 변화를 나타내는 도메인 이벤트

메시지 채널

  • 점대점 채널: 채널을 읽는 컨슈머 중 딱 하나만 지정해서 메시지 전달 (일대일)
  • 발행-구독 채널: 같은 채널을 바라보는 모든 컨슈머에 메시지 전달 (일대다)

메시징 상호 작용 스타일 구현

요청/응답 및 비동기 요청/응답
클라이언트/서비스는 한 쌍의 메시지를 주고받는 비동기 요청/응답 스타일로 상호 작용한다.

  • 클라이언트는 커맨드 메시지를 서비스가 소유한 점대점 메시징 채널에 보낸다.
  • 서비스는 요청을 처리한 후 응답 메시지를 클라이언트가 소유한 점대점 채널로 돌려보낸다.

MessageId, CorrelationId 로 요청을 맞출 수 있다.

발행/구독

  • 클라이언트: 여러 컨슈머가 읽는 발행/구독 채널에 메시지 발행
  • 서비스: 도메인 객체의 변경 사실을 알리는 도메인 이벤트 발행

예를 들어 주문 서비스는 Order 이벤트를 Order 채널에 발행하고, 배달 서비스는 Delivery 이벤트를 Delivery 채널에 발행, 서비스는 자신이 관심있는 도메인 객체의 이벤트 채널을 구독

메시지 브로커

메시지 브로커는 모든 메시지가 지나가는 중간 지점

  • 송신자 -> 메시지 브로커 -> 수신자

    • 송신자는 컨슈머의 네트워크 위치를 몰라도 된다.
  • 메시지 브로커 선택할 때의 검토 항목

    • 프로그래밍 언어 지원 여부, 메시징 표준 지원 여부, 메시지 순서, 전달 보장, 영속화, 내구성, 확장성, 지연 시간, 경쟁사 컨슈머 등등
  • 예시

    • ActiveMQ, RabbitMQ, 카프카, AWS 키네시스, AWS SQS

장점

  • 느슨한 결합: 클라는 서비스 인스턴스를 몰라도 되므로 서비스 인스턴스 위치를 알려 주는 디스커버리 메커니즘 필요없음
  • 메시지 버퍼링: 처리 가능한 시점까지 메시지 버퍼링 가능
  • 명시적 IPC: 원격 서비스가 마치 자신이 로컬 서비스인 양 호출 시도

단점

  • 성능 병목 가능성
  • 단일 장애점 가능성
  • 운영 복잡도 부가

메시지 순서 유지

주문 생성, 변경, 취소 이러한 이벤트 메시지는 차례로 메시지를 전송할 필요가 있다. 순서를 유지하기 위해 샤딩된 채널을 이용한다.

  • orderId를 샤드 키로 둬서, 주문별 이벤트는 동일한 샤드에 발행되도록 한다. 따라서 한 컨슈머 인스턴스만 메시지를 읽도록 하여 메시지 처리 순서가 보장된다.

중복 메시지 처리

  1. 멱등한 메시지 핸들러 작성
  2. 메시지 추적과 중복 메시지 솎아 내기
  • 컨슈머가 메시지 ID를 이용해 메시지 처리 여부를 추적하면서 중복 메시지 솎아 내면 된다. 예를 들어 소비하는 메시지 ID는 무조건 DB 테이블에 저장!

트랜잭셔널 메시징

서비스는 보통 DB를 업데이트하는 트랜잭션의 일부로 메시지를 발행한다. DB 업데이트와 메시지 전송을 한 트랜잭션으로 묶지 않으면 DB 업데이트 후 메시지는 아직 전송되지 않은 상태에서 서비스가 중단될 수 있다.

DB 테이블을 메시지 큐로 활용

  • DB에 쓰고, OUTBOX 테이블을 읽는 방법
  • 메시지 릴레이는 OUTBOX 테이블을 읽어 메시지 브로커에 메시지를 발행하는 컴포넌트

이벤트 발행: 폴링 발행기 패턴

  • 테이블을 폴링해서 미발행 메시지 조회한 후 각자의 목적지 채널로 보내서 메시지 브로커에 발행
    • select * from outbox ordered by ... asc
  • 그 후 나중에 OUTBOX 테이블에서 메시지 삭제

자주 폴링하면 비용이 유발되고 NoSQL DB는 쿼리 능력에 따라 사용 가능 여부 결정, 이 방식은 규모가 작을 때 가능

이벤트 발행: 트랜잭션 로그 테일링 패턴

  • 트랜잭션 로그 마이너로 DB 트랜잭션 로그를 읽어 변경분을 하나씩 메시지로 메시지 브로커에 발행하는 방식

실제 응용 사례

  • 디비지움, 링크드인 데이터버스, DynamoDB 스트림즈, 이벤추에이트 트램

메시징 라이브러리/프레임워크

서비스가 메시지를 주고받으려면 라이브러리가 필요하다. 이 책에선 예시로 이벤추에이트 트램 프레임워크를 사용한다.

이벤추에이트 트램에는 중요한 메커니즘 두 가지가 구현되어 있다.
1. 트랜잭셔널 메시징
2. 중복 메시지 감지

3.4 비동기 메시징으로 가용성 개선

동기 통신으로 인한 가용성 저하

동기 방식은 호출한 서비스가 응답할 때까지 HTTP 클라이언트가 마냥 기다려야 한다. 따라서 서비스가 동기 프로토콜로 통신하면 가용성은 저하될 수 밖에 없다.

동기 상호 작용 제거

비동기 상호 작용 스타일

  • 메시지가 소비되는 시점까지 메시지 브로커가 메시지를 버퍼링하므로 탄력적이다.

데이터 복제

서비스에 동기 API가 있는 경우 데이터를 복제하면 가용성을 높일 수 있다. 데이터를 복제해둬서 다른 서비스와 상호 작용하지 않도록 한다.

응답 반환 후 마무리

  1. 로컬에서 가용한 데이터만 갖고 요청 검증
  2. 메시지를 OUTBOX 테이블에 삽입하는 식으로 DB 업데이트
  3. 클라이언트에 응답 반환

일단 냅다 PENDING 상태 생성하고 클라에 응답 반환하고 나머지 동작들은 비동기로 마무리

🔗 Reference

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글