마이크로서비스 Microservices (3) 프로세스 간 통신

🗓 ⏱ 소요시간 6 분

프로세스 간 통신

모놀리식(monolithic) 애플리케이션에서는 단순하게 다른 메소드나 함수를 호출하면 됩니다. 하지만 마이크로서비스(Microservices)에서는 서비스 단위로 나뉘어져 있는 분산 시스템이기 때문에 서비스 간 통신이 필요합니다. 이러한 통신을 프로세스 간 통신(inter-process communication)이라고 합니다. 다음 그림에서 왼쪽이 모놀리스, 오른쪽이 마이크로서비스입니다.

https://www.nginx.com/blog/building-microservices-inter-process-communication/

통신 방식

통신 방식은 먼저 호출하는 쪽과 호출당하는 쪽의 수로 구분해볼 수 있습니다. 하나의 요청이 하나의 서비스를 실행하면 일대일(One-to-One), 하나의 요청이 여러 서비스를 실행하면 일대다(One-to-Many)라고 볼 수 있습니다.

그리고 동기(Synchronous)와 비동기 방식(Asynchronous)으로 구분할 수 있습니다. 동기 방식은 요청을 보내고 응답이 올 때까지 기다리는 방식으로 이후 동작은 멈춘 상태(block)가 됩니다. 그리고 응답을 받은 후에 처리합니다. 반대로 비동기 방식은 요청을 보내고 응답이 올 때까지 기다리지 않고 다음을 실행합니다. 그리고 응답을 처리하는 코드를 같이 보내서 작업이 끝나면 실행하게 됩니다. 이러한 함수를 콜백 함수(callback function)라고 합니다.

이 두 가지 구분 방식을 조합하면 다음과 같습니다.

일대일 일대다
동기 1) 요청/응답(Request/response) -
비동기 2) 알림(Notification) 4) 퍼블리시/구독
(Publish/subscribe)
3) 요청/비동기 응답
(Request/async response)
5) 퍼블리시/비동기 구독
(Publish/async responses)
  1. 요청/응답 : 요청을 보내고 응답이 올 때까지 기다립니다.
  2. 알림 : 요청을 보내기만 합니다. 모바일의 푸시 알림을 생각하시면 됩니다.
  3. 요청/비동기 응답 : 요청을 보내면 비동기로 응답이 옵니다.
  4. 퍼블리시/구독 : 등록된 서비스들에 요청을 보냅니다. 요청을 받은 서비스들은 각자 로직을 처리합니다. 잡지를 구독하는 것과 같다고해서 퍼블리시/구독 방식이라고 합니다.
  5. 퍼블리시/비동기 응답 : 위와 같지만 비동기 형태로 응답을 보낸다는 점이 다릅니다.

여러가지 방식을 살펴봤는데요, 애플리케이션에 따라서 하나의 방식만으로도 충분하기도 하고, 여러 방식을 함께 사용하기도 합니다. 이전 포스트에서 살펴본 택시 호출 서비스를 예로 들어보겠습니다.

https://www.nginx.com/blog/building-microservices-inter-process-communication/

  1. 승객이 스마트폰으로 픽업을 요청합니다. 이 요청은 트립 관리 서비스를 호출합니다(알림)
  2. 트립 관리 서비스는 승객 관리 서비스에서 승객 정보를 확인합니다(요청/응답)
  3. 트립 관리 서비스는 해당 트립을 디스패와처와 가까운 택시기사에게 보냅니다(요청/응답)

API 정의와 수정

API 는 서비스와 클라이언트 간 일종의 계약입니다. 그래서 API 를 제대로 정의하는 것이 중요합니다. 그래서 API 먼저 설계하고 클라이언트 개발자들과 리뷰를 통해서 확정 짓고 개발을 진행하는 것이 좋습니다. 나중에 API를 수정하는 것보다 합리적인 방법이죠.

처음에 API 를 잘 정의하는 것만큼 API 를 수정하는 것도 중요한 일입니다. 특히 마이크로서비스 기반의 애플리케이션에서는 해당 API 를 사용하는 서비스들이 많기 때문에 수정의 영향이 상당히 큽니다. 요청, 응답에 속성이 추가되는 정도의 마이너한 수정은 API 내에서 호환성을 지원할 수 있습니다.

하지만 메이저한 수정이 필요할 때도 있습니다. 이럴 땐 API를 사용하는 클라이언트가 당장 업그레이드할 수 없으니 수정할 시간을 줘야합니다. 그래서 옛날 버전과 새로운 버전을 한동안 같이 지원해야 하고 HTTP 같은 경우에는 URL 에 버전을 포함시키는 것이 일반적입니다.

IPC 기술

IPC 기술들은 다음과 같이 구분할 수 있습니다.

여기서 사용하는 메시지 포맷도 여러가지가 있죠.

  • 텍스트 기반(사람이 쉽게 읽을 수 있음): JSON, XML
  • 바이너리 포맷(성능 상 좋음): Apache Avro, Protocol Buffers

자세한 내용을 살펴보겠습니다.

동기 요청/응답

클라이언트에서 서비스로 요청을 보내면 해당 서비스가 요청을 처리해서 결과로 응답을 보내는 방식입니다. 요청을 보낸 클라이언트의 쓰레드는 응답이 올 때까지 기다리며 블락(block)됩니다. 물론 FutureReactiveX 를 이용해서 이벤트 기반으로 코드를 짜면 비동기로 처리할 수 있습니다.

REST

상당히 많은 API 가 HTTP 기반의 RESTful API 로 작성되어 있습니다. REST 는 웹 상에 리소스를 지정하는 방식으로 HTTP 동사와 URL 을 조합해서 사용합니다. URL 로는 특정 리소스(CustomerProduct 같은 비즈니스 개체)를 지정하고 HTTP 동사로 동작을 지정합니다.

  • GET : 조회(Read)
  • POST : 생성(Create)
  • PUT: 업데이트(Update)
  • DELETE: 삭제(Delete)

https://www.nginx.com/blog/building-microservices-inter-process-communication/

택시 호출 애플리케이션 예제로 돌아가볼까요? 승객이 택시를 부를 때 REST API 를 호출합니다. POST 방식으로 URL은 /trips 인데요 트립 관리 서비스에 새로운 트립 정보가 생성된다는걸 알 수 있습니다. 트립 관리 서비스에서는 승객에 대한 정보를 조회하기 위해서 승객 관리 서비스가 제공하는 API에 GET 방식으로 /passensgers/passengerId 의 요청을 보냅니다. 그럼 해당 승객 ID 로 승객 정보를 조회할 수 있습니다. 응답은 처리 결과를 나타내는 응답 코드를 가지고 있는데 각각 200, 201 코드를 반환하게 됩니다.

REST 는 직관적이고 기존 HTTP 웹 인프라를 그대로 사용할 수 있어 사용하기 쉽습니다. 하지만 API 가 HTTP 기반이라고 해서 모두 RESTful 한 것은 아닙니다.[1] 명확한 표준이 없다보니 잘못 설계된 API 도 많고 무엇보다 복잡한 비즈니스 로직을 표현하기가 어렵습니다. 위에서 사용한 HTTP 동사들은 기본적인 CRUD 기능을 나타내고 있죠. RAML 이나 Swagger 같은 API 설계 도구를 사용해 쉽고 제대로 된 Restful API를 설계할 수 있습니다.

Apache Thrift

Apache Trhift 는 REST의 대안 중 하나입니다. Thrift 를 이용해 API를 정의하면 C++, Java, Python, PHP, Ruby, Node.js 등 여러 언어의 코드를 생성해줍니다. REST의 대안 답게 요청/응답 뿐만 아니라 단방향 전송도 지원하고 JSON, 바이너리 등 다양한 메시지 포맷을 지원합니다.

비동기 메시지 기반 통신

이번엔 비동기 방식을 살펴보겠습니다. 비동기 방식은 메시지를 보내놓고 응답을 기다리지 않습니다. 이 메시지는 헤더(header)와 바디(body)로 구성되어 있고 채널(channel)을 통해 전송됩니다. 메시지는 한 곳에만 보낼 수도 있고(일대일), 옵저버 패턴같은 퍼블리시-구독 모델을 따라 여러 곳에 메시지를 보낼 수도 있습니다(일대다). 그 메시지를 받겠다고 구독해놓은 서비스에게 모두 메시지가 전송되는 거죠.

https://www.nginx.com/blog/building-microservices-inter-process-communication/

택시 호출 예제로 돌아가봅시다. 동기 방식과는 완전히 다른 모습이죠. 트립이 생성되었다는 메시지를 NEW TRIPS 채널에 전송하면 해당 채널을 구독하고 있는 디스패처가 받아 택시 기사를 배정합니다. 그리고 나서 배정 결과를 DISPATCHING 채널에 전송하면 해당 채널을 구독하고 있는 트립, 승객, 택시기사 관리 서비스에서 한번에 메시지를 받게 됩니다.

이런 메시징 방식은 클라이언트와 서비스 사이의 의존도를 줄여줍니다. 동기 방식은 클라이언트와 서비스가 서로를 알아야 하고 직접 통신하는 구조이지만 메시징 방식은 그 사이에 메시징 시스템이 들어가서 간접적으로 통신하기 때문이죠. 일대일 뿐만 아니라 일대다 통신을 지원하는 것도 장점입니다. 그리고 메시징 시스템은 전송되는 메시지가 많을 때 버퍼를 활용해 속도를 조절할 수도 있습니다. 물론 메시징 시스템이 추가로 들어가는만큼 운영하고 관리해야 할 것이 늘어나게 됩니다.

메시지를 전송하는 표준 프로토콜은 AMQP 와 STOMP가 있습니다. 그리고 오픈소스 메시징 시스템에는 RabbitMQ, Apache Kafka, Apache ActiveMQ, NSQ 등이 있습니다.

메시지 포맷

통신 방식을 골랐다면 메시지를 어떤 포맷으로 보낼지 골라야 합니다.

먼저 사람이 읽기 쉽고 이해할 수 있는 XML 과 JSON 이 있습니다. XML 은 XML 스키마로 구조를 정의할 수가 있어서 데이터를 쉽게 검증할 수 있지만 메시지 사이즈가 너무 커집니다. JSON 은 XML 보다 가볍지만 데이터를 검증하기가 쉽지 않았습니다. 그래서 JSON 에서도 XML 처럼 데이터를 검증할 수 있도록 만든 것이 JSON 스키마 입니다.

이런 텍스트 기반 메시지는 텍스트를 파싱하는데 오버헤드가 생깁니다. 따라서 성능을 위해 바이너리 값으로 전송하는 방법도 있습니다. 많이 쓰이는 것으로는 binary Thrift 나 Protocol Buffers, Apache Avro. 등이 있습니다.

참고