본문 바로가기

Archived(IT)/MSA

마이크로 서비스 패턴 #3 IPC

MSA 에서 왜 통신이 필요한가?

당연한 이야기이다.

서비스가 나누어져 있고 메모리가 독립적으로 구성된다. 원래 기존의 모놀리틱 서비스의 경우 하나의 메모리 안에서 얼마든지 다른 서비스(메소드)의 메모리를 주소로서 참조가능했다. 즉, 접근가능해서 input과 output을 스레드 내에서 얼마든지 주고 받을 수 있었다.

 

그러나 서비스가 나누어지면서 메모리 상으로 독립된 공간에서 서비스(메서드)들이 존재하게 되고 하나의 스레드 안에서 다른 독립된 메모리 상의 주소에 접근할 수 없게 되었다. 그러니 스레드들 간에 어떠한 방식으로든 통신이 필요하게 된 것이다. 그 방식의 대표적인 방식으 API 형태가 될 수 있겠다.

 

상호작용 스타일

통신에는 크게 2가지로 나눠볼 수 있다.

일대일/일대다

  • 일대일 : 각 클라이언트 요청은 정확히 한 서비스가 처리
  • 일대다 : 각 클라이언트 요청을 여러 서비스가 협동하여 처리

동기/비동기

  • 동기 : 클라이언트는 서비스가 제시간에 응답하리라 기대하고 대기 도중 블로킹 가능
  • 비동기 : 클라이언트가 블로킹하지 않음. 응답은 즉시 전송되지 않음.

일대일 상호작용의 종류

  • 요청/응답 : 클라이언트에서 서비스를 요청하고 응답을 대기한다(블로킹 가능, 강한 결합성)
  • 비동기 요청/응답 : 클라이언트에서 서비스 요청하고 서비스는 비동기적으로 응답(블로킹 불가)
  • 단방향 알림 : 클라이언트는 일방적으로 요청만하고 서비스는 응답을 보내지 않음(알림성)

 

일대다 상호작용의 종류

  • 발행/구독 : 클라에서 알림 메세지를 발행하면, 특정 서비스가 메세지를 소비
  • 발행/비동기 응답 : 클라에서 요청 메세지를 발행하면 특정 시간동안 특정 서비스가 응답하길 대기

 

마이크로 서비스 API

API는 어떤 IPC 방식을 사용하느냐에 따라 그 내용이 결정됨.

API를 발전시키는 데 있어 시맨틱 버저닝 명세를 따라야 한다.

 

다음의 용어로 API 변화를 구분한다

  • MAJOR: 하위 호환되지 않는 변경분 API 적용
  • MINOR: 하위 호환되는 변경분 API 적용
  • PATCH: 하위 호환되는 오류 수정

IPC의 핵심은 메세지 교환이다. 따라서 메세지 포맷형태를 잘 명세된 문서에 기인하여 작성해야만 한다.

JSON, XML 등 텍스트 기반이 있을 수 있고, 프로토콜 버퍼와 아브르 같은 이진 메세지 포맷이 있을 수 있다.

 

동기 RPI의 패턴: REST, 왜 REST여야 하는가

MSA의 서비스 마다 앞단의 인터페이스로 구성
해당 웹 서버로 서비스를 호출 -> 응답 제공의 통신 형태가 이루어져 서비스가 돌아간다.


HTTP로 통신 마구할 수는 없다. 부하가 많이 걸린다. 어떤 데이터인지와 더불어 어떤 방식으로 처리할 건지 이런 내용을 전부 보내야 한다. 그래서 REST API로 효율적으로 통신하겠다. 자원과 방법을 명세하여 효율적으로 통신한다.

https://qastack.kr/programming/2190836/what-is-the-difference-between-http-and-rest

 

REST의 성숙도 레벨(레너드 리처드슨)
  • 레벨 0: 클라이언트는 서비스별로 유일한 URL 끝점에 HTTP POST 요청을 하여 서비스를 호출합니다. 요청을 할 때마다 어떤 액션을 수행할지, 그 대상(예: 비즈니스 객체)은 무엇인지 지정합니다. 필요한 매개변수도 함께 전달합니다.
  • 레벨 1: 서비스는 리소스 개념을 지원합니다. 클라이언트는 수행할 액션과 매개변수가 지정된 POST 요청을 합니다.
  • 레벨 2: 서비스는 HTTP 동사를 이용해서 액션을 수행하고(예: GET은 조회, POST는 생성, PUT은 수정), 요청 쿼리 매개변수 및 본문, 필요 시 매개변수를 지정합니다. 덕분에 서비스는 GET 요청을 캐싱하는 등 웹 인프라를 활용할 수 있습니다.
  • 레벨 3: 서비스를 HATEOAS(Hypertext As The Engine Of Application State, 애플리케이션 상태 엔진으로서의 하이퍼미디어) 원칙에 기반하여 설계합니다. HATEOAS는 GET 요청으로 반환된 리소스 표현형에 그 리소스에 대한 액션의 링크도 함께 태워 보내자는 생각입니다. 가령 클라이언트는 GET 요청으로 주문 데이터를 조회하고 이때 반환된 표현형 내부 링크를 이용해서 해당 주문을 취소할 수도 있습니다. HATEOAS를 사용하면 하드 코딩한 URL을 클라이언트 코드에 욱여넣지 않아도 됩니다.
RSET API를 고려할 때

요청 한 번으로 연관된 리소스를 허용할 것인가(주문, 고객 서비스에 대해서 두 번 요청할 것인가?)

작업을 HTTP Method에 늘 맵핑할 수 있을까(꼭 GET POST PUT DELETE 로 다 표현 가능할까?)

 

장점 

  • 단순하고 익숙합니다.
  • 포스트맨(Postman) 같은 브라우저 플러그인이나 curl 등의 CLI 도구로 HTTP API 테스트 가능합니다.
  • 요청/응답 스타일의 통신을 직접 지원합니다.
  • HTTP는 방화벽 친화적(firewall friendly)입니다.
  • 중간 브로커가 필요하지 않기 때문에 시스템 아키텍처가 단순해집니다.

단점

  • 요청/응답 스타일의 통신만 지원합니다.
  • 가용성이 떨어집니다. 중간에서 메시지를 버퍼링하는 매개자 없이 클라이언트/서비스가 직접 통신하기 때문에 교환이 일어나는 동안 양쪽 다 실행 중이어야 합니다.
  • 서비스 인스턴스(들)의 위치(URL)를 클라이언트가 알고 있어야 합니다. 요즘 애플리케이션은 서비스 디스커버리 메커니즘을 이용해서 클라이언트가 서비스 인스턴스 위치를 찾을 수 있으므로 큰 단점은 아닙니다(3.2.4절).
  • 요청 한 번으로 여러 리소스를 가져오기 어렵습니다.
  • 다중 업데이트 작업을 HTTP 동사에 매핑하기 어려울 때가 많습니다.
동기 RPI의 패턴: gRPC

HTTP의 한정된 메소드를 극복하고자 등장한 기술(HTTP/2 방식이다)

다양한 언어로 클라이언트/서버를 작성할 수 있는 프레임워크이다.

메세지 기반의 프로토콜이므로 서비스를 API 우선으로 설계해야 한다.

 

gRPC API는 하나 이상의 서비스와 요청/응답 메세지 정의로 구성된다.

또한 프로토콜 버퍼 메세지 포맷을 사용한다(효율적이다)

  • 이진 프레이밍 및 압축. HTTP/2 프로토콜은 간단하며, 보내고 받을 때 모두 효율적입니다.
  • 단일 TCP 연결보다 여러 HTTP/2 호출의 멀티플렉싱. 멀티플렉싱은 HOL(Head of Line) 차단을 제거합니다

gPRC vs JSON HTTP API

기능 gRPC JSON을 사용하는 HTTP API
계약 필수( .proto) 선택 사항(OpenAPI)
프로토콜 HTTP/2 HTTP
Payload Protobuf(소형, 이진) JSON(대형, 사람이 읽을 수 있음)
규범 엄격한 사양 느슨함. 모든 HTTP가 유효합니다.
스트리밍 클라이언트, 서버, 양방향 클라이언트, 서버
브라우저 지원 아니요(gRPC-웹 필요)
보안 전송(TLS) 전송(TLS)
클라이언트 코드 생성 OpenAPI + 타사 도구
서비스 디스커버리

REST API든 gRPC 이든 서비스를 호출하는 코드를 개발하면 이 호출 코드가 어디 있는지 

이런 부분은 서비스들끼리 약속되어야 한다. 네트워크 상에서 식별하고 서비스가 부드럽게 흘러가기 위해서는 서비스 디스커버리의 존재가 필수적이다. 서비스 인스턴스의 동적 위치도 파악하고 여러 사유로 달라지는 클라리언트 코드들도 서비스 디스커버리가 해결해줄 수 있다. 

 

애플리케이션 수준의 서비스 디스커버리를 적용할 수도 있고

플랫폼에 내장된 서비스 디스커버리를 적용할 수도 있다.

 

비동기 통신 : 메시징 큐

동기 방식으로 원트랜잭션으로 블로킹이 이루어진다?

그러면 서비스들 간에 수많은 통신에서 대기가 이루어지게 되고 잠시의 대기 타임이 전체 서비스 성능의 큰 저하를 일으킬 수 있다. 이는 MSA 단점을 치명적으로 드러낸다.

 

이에 대해 비동기 방식 메시징 큐를 활용할 수 있다.

 

메시지 : 서비스가 비동기적으로 주고받는 통신(문서/커맨드/이벤트로 구분)

  • 문서 :  데이터만 포함된 제네릭한 메시지(데이터를 어떻게 할지는 수신자가 결정)
  • 커맨드 : RPC 요청과 동등한 메시지(호출할 작업과 전달할 매개변수 지정)
  • 이벤트 : 송신자에게 어떤 사건이 발생했음을 알리는 메시지

단방향 알림, 발행/구독, 발행/비동기 응답의 세가지 형태가 대표적인 구현 스타일이다.

 

비동기 통신 : 메시지 브로커

메시지 브로커: 메시징 기반의 애플리케이션은 대부분 메시지 브로커를 이용한다.

서비스가 서로 통신할 수 있게 해주는 인프라 서비스이다. 서비스들 간에 직접 통신이 아닌 브로커를 통한 통신은 장단점이 있다.

장점

  • 송신자가 보낸 메시지가 브로커를 거쳐 수신자로 이동하는 것이 아니라, 송신자에서 수신자로 직접 전달되므로 네트워크 트래픽이 가볍고 지연 시간이 짧습니다.
  • 메시지 브로커가 성능 병목점이나 SPOF(Single Point Of Failure, 단일 장애점)가 될 일이 없습니다.
  • 시지 브로커를 설정/관리할 필요가 없으므로 운영 복잡도가 낮습니다.

 

단점

  • 서비스가 서로의 위치를 알고 있어야 하므로 서비스 디스커버리 메커니즘(3.2.4절) 중 하나를 사용해야 합니다.
  • 메시지 교환 시 송신자/수신자 모두 실행 중이어야 하므로 가용성이 떨어집니다.
  • 전달 보장(guaranteed delivery) 같은 메커니즘을 구현하기가 더 어렵습니다.

메시지 브로커의 제품으로 대표적으로 아파치 카프카(Kafka), AtciveMQ, RabbitMQ가 있다.

(OS 내부의 프로세스 간 통신에서 RabbitMQ를 통해 함수 후킹을 처리해본적이 있었는데 이게 메세지 브로커인줄도 방금 알게 되었다)

 

비동기 통신 : 메시징 처리에서 그 외 

수신자들 간의 Race Condition과 메시징 순서 유지

메시지 브로커가 샤딩된 채널을 통해 처리할 때 논리적 순서를 유지한다!

 

중복된 메세지를 처리하기 위해 멱등한 핸들러를 작성하기

(멱등하다 : 동일 인풋에 대해 반복 호출이 이루어져도 부작용이 없다)

 

서비스 전체 트랜잭션 관리는 어떻게..? 

예전엔 메시지 브로커가 DB에 접근하는 형식 -> 비효율적이다.

DB 테이블 자체를 메시지 큐로 활용하는 형태도 채택된다.

 

결론적으로, 동기 통신에 따른 문제점을 해결하고자 비동기 통신으로 이를 커버하고

대표적으로 메시징 큐가 활용되는데 이는 메시지 브로커를 통해 메시징의 통신을 원활히 하고

다양한 비이상적인 상황들에 대처할 수 있는 가장 효율적인 서비스 방식으로 채택되고 각광받고 있다.

(실제 운용되는 많은 서비스들은 비동기 통신을 채택하고 메시징 브로커로 kafka를 선택하고 있다)