마이크로서비스 구축: 마이크로서비스 아키텍처의 프로세스 간 통신

NGINX | 2015년 7월 24일

편집자 – 총 7부로 구성된 이 연재 기사가 이제 완결되었습니다:

  1. 마이크로서비스 소개
  2. 마이크로서비스 구축: API 게이트웨이 사용
  3. 마이크로서비스 구축: 마이크로서비스 아키텍처의 프로세스 간 통신(이 글)
  4. 마이크로서비스 아키텍처의 서비스 디스커버리
  5. 마이크로서비스를 위한 이벤트 기반 데이터 관리
  6. 마이크로서비스 배포 전략 선택
  7. 모놀리스를 마이크로서비스로 리팩터링하기

전체 아티클과 NGINX Plus를 사용한 마이크로서비스 구현 정보를 전자책으로도 다운로드할 수 있습니다. 전자책 제목은 마이크로서비스: 설계부터 배포까지입니다. 또한 새로운 마이크로서비스 솔루션 페이지도 확인해 보세요.

마이크로서비스 아키텍처로 애플리케이션을 구축하는 방법을 다루는 시리즈의 세 번째 글입니다. 첫 번째 글에서는 마이크로서비스 아키텍처 패턴을 소개하고 이를 모놀리식 아키텍처 패턴과 비교하며, 마이크로서비스 사용의 장단점을 살펴봅니다. 두 번째 글에서는 애플리케이션 클라이언트가 API 게이트웨이라는 중개자를 통해 마이크로서비스와 통신하는 방법을 설명합니다. 이 글에서는 시스템 내 서비스들이 서로 통신하는 방법을 살펴봅니다. 네 번째 글에서는 이와 밀접하게 관련된 서비스 디스커버리 문제를 다룹니다.

소개

모놀리식 애플리케이션에서는 컴포넌트가 언어 수준의 메서드 또는 함수 호출을 통해 서로를 호출합니다. 반면 마이크로서비스 기반 애플리케이션은 여러 머신에서 실행되는 분산 시스템입니다. 각 서비스 인스턴스는 일반적으로 하나의 프로세스입니다. 따라서 다음 다이어그램에서 볼 수 있듯이 서비스는 프로세스 간 통신(IPC) 메커니즘을 사용하여 상호 작용해야 합니다.


마이크로서비스 애플리케이션에서는 서비스 간 프로세스 간 통신(IPC) 메커니즘이 필요합니다(반면 모놀리식 애플리케이션의 모듈은 루틴을 직접 호출할 수 있습니다).

구체적인 IPC 기술은 나중에 살펴보고, 먼저 다양한 설계 문제를 알아보겠습니다.

상호작용 방식

서비스에 사용할 IPC 메커니즘을 선택할 때는 먼저 서비스가 어떻게 상호 작용하는지 생각해 보는 것이 좋습니다. 클라이언트⇔서비스 간에는 다양한 상호 작용 방식이 있습니다. 이러한 방식은 두 가지 차원으로 분류할 수 있습니다. 첫 번째 차원은 상호 작용이 일대일인지 일대다인지 여부입니다:

  • 일대일 – 각 클라이언트 요청은 정확히 하나의 서비스 인스턴스에서 처리됩니다.
  • 일대다 – 각 요청은 여러 서비스 인스턴스에서 처리됩니다.

두 번째 기준은 상호작용이 동기식인지 비동기식인지 여부입니다:

  • 동기식 – 클라이언트는 서비스가 적시에 응답할 것으로 기대하며, 응답을 기다리는 동안 차단될 수도 있습니다.
  • 비동기식 – 클라이언트는 응답을 기다리는 동안 차단되지 않으며, 응답이 있더라도 반드시 즉시 전송되는 것은 아닙니다.

다음 표에서는 다양한 상호 작용 방식을 보여 줍니다.

 일대일일대다
동기식요청/응답 — 
비동기식알림게시/구독
요청/비동기 응답게시/비동기 응답

일대일 상호작용에는 다음과 같은 유형이 있습니다:

  • 요청/응답 – 클라이언트가 서비스에 요청을 보내고 응답을 기다립니다. 클라이언트는 응답이 적시에 도착할 것으로 기대합니다. 스레드 기반 애플리케이션에서는 요청을 보낸 스레드가 응답을 기다리는 동안 차단될 수도 있습니다.
  • 알림(단방향 요청이라고도 함) – 클라이언트가 서비스에 요청을 보내지만 응답을 기대하지 않으며 서비스도 응답을 보내지 않습니다.
  • 요청/비동기 응답 – 클라이언트가 서비스에 요청을 보내면 서비스는 비동기적으로 응답합니다. 클라이언트는 응답을 기다리는 동안 차단되지 않으며, 응답이 도착하기까지 시간이 걸릴 수 있다는 전제로 설계됩니다.

일대다 상호 작용에는 다음과 같은 유형이 있습니다:

  • 게시/구독 – 클라이언트가 알림 메시지를 게시하면, 관심 있는 서비스가 이를 0개 이상 수신합니다.
  • 게시/비동기 응답 – 클라이언트가 요청 메시지를 게시한 다음, 관련 서비스의 응답을 일정 시간 동안 기다립니다.

각 서비스는 일반적으로 이러한 상호작용 방식들을 조합하여 사용합니다. 일부 서비스에는 단일 IPC 메커니즘만으로 충분하지만, 다른 서비스에는 여러 IPC 메커니즘을 조합하여 사용해야 할 수도 있습니다. 다음 다이어그램은 사용자가 차량 호출을 요청할 때 택시 호출 애플리케이션의 서비스들이 어떻게 상호작용하는지를 보여줍니다.


서비스는 알림, 요청/응답, 게시/구독 방식을 조합하여 사용합니다. 예를 들어 승객의 스마트폰은 픽업을 요청하기 위해 Trip Management 서비스에 알림을 보냅니다. Trip Management 서비스는 요청/응답 방식으로 Passenger Service를 호출하여 승객의 계정이 활성 상태인지 확인합니다. 그런 다음 Trip Management 서비스는 운행을 생성하고 게시/구독 방식을 사용하여 이용 가능한 운전자를 찾는 Dispatcher를 비롯한 다른 서비스에 알립니다.

지금까지 상호 작용 스타일을 살펴보았으니, 이제 API를 정의하는 방법을 알아보겠습니다.

API 정의

서비스의 API는 서비스와 클라이언트 간의 계약입니다. 어떤 IPC 메커니즘을 선택하든 일종의 인터페이스 정의 언어(IDL)를 사용하여 서비스의 API를 정확하게 정의하는 것이 중요합니다. 서비스를 정의할 때 API 우선 접근 방식을 사용해야 한다는 타당한 주장도 있습니다. 서비스 개발은 인터페이스 정의를 작성하고 클라이언트 개발자와 함께 검토하는 것부터 시작합니다. API 정의를 반복적으로 다듬은 후에야 서비스를 구현합니다. 이처럼 설계를 미리 수행하면 클라이언트의 요구 사항을 충족하는 서비스를 구축할 가능성이 높아집니다.

이 글의 뒷부분에서 살펴보겠지만, API 정의의 성격은 사용하는 IPC 메커니즘에 따라 달라집니다. 메시징을 사용하는 경우 API는 메시지 채널과 메시지 유형으로 구성됩니다. HTTP를 사용하는 경우 API는 URL과 요청 및 응답 형식으로 구성됩니다. 뒤에서 몇 가지 IDL을 더 자세히 설명하겠습니다.

API 발전시키기

서비스의 API는 시간이 지남에 따라 필연적으로 변경됩니다. 모놀리식 애플리케이션에서는 일반적으로 API를 변경하고 모든 호출자를 업데이트하기가 간단합니다. 마이크로서비스 기반 애플리케이션에서는 API의 모든 소비자가 동일한 애플리케이션 내의 다른 서비스라 하더라도 훨씬 더 어렵습니다. 일반적으로 모든 클라이언트가 서비스와 동시에 업그레이드하도록 강제할 수 없습니다. 또한 서비스의 새 버전을 점진적으로 배포하여 서비스의 이전 버전과 새 버전이 동시에 실행되도록 할 가능성이 높습니다. 이러한 문제에 대응하기 위한 전략을 마련하는 것이 중요합니다.

API 변경을 처리하는 방식은 변경 규모에 따라 달라집니다. 일부 변경은 사소하며 이전 버전과 하위 호환됩니다. 예를 들어 요청이나 응답에 속성을 추가할 수 있습니다. 클라이언트와 서비스가 견고성 원칙을 따르도록 설계하는 것이 좋습니다. 이전 API를 사용하는 클라이언트도 새 버전의 서비스에서 계속 작동해야 합니다. 서비스는 누락된 요청 속성에 기본값을 제공하고, 클라이언트는 추가된 응답 속성을 무시합니다. API를 쉽게 발전시킬 수 있는 IPC 메커니즘과 메시징 형식을 사용하는 것이 중요합니다.

하지만 때로는 API에 호환되지 않는 중대한 변경을 적용해야 합니다. 클라이언트가 즉시 업그레이드하도록 강제할 수 없으므로 서비스는 일정 기간 동안 이전 버전의 API를 지원해야 합니다. REST와 같은 HTTP 기반 메커니즘을 사용하는 경우 URL에 버전 번호를 포함하는 방법이 있습니다. 각 서비스 인스턴스가 여러 버전을 동시에 처리하도록 할 수도 있습니다. 또는 특정 버전만 처리하는 인스턴스를 버전별로 따로 배포할 수도 있습니다.

부분 장애 처리

앞서 API Gateway<.htmla>에 관한 이전 글에서 언급했듯이, 분산 시스템에는 부분 장애의 위험이 항상 존재합니다. 클라이언트와 서비스는 별도의 프로세스이므로 서비스가 클라이언트의 요청에 제때 응답하지 못할 수 있습니다. 장애나 유지보수로 인해 서비스가 중단될 수도 있습니다. 또는 서비스에 과부하가 걸려 요청에 매우 느리게 응답할 수도 있습니다.

예를 들어, 해당 글에서 다룬 제품 상세 정보 시나리오를 살펴보겠습니다. 추천 서비스가 응답하지 않는다고 가정해 보겠습니다. 단순하게 구현된 클라이언트는 응답을 기다리며 무기한 차단될 수 있습니다. 이는 사용자 경험을 저해할 뿐만 아니라, 많은 애플리케이션에서 스레드와 같은 귀중한 리소스를 소모하게 됩니다. 결국 런타임에서 사용할 수 있는 스레드가 고갈되어 다음 그림과 같이 응답하지 않는 상태가 됩니다.


마이크로서비스 애플리케이션은 부분 장애를 처리할 수 있도록 설계해야 합니다. 그렇지 않으면 클라이언트가 응답하지 않는 서비스를 기다리며 차단될 때 런타임의 스레드가 고갈될 수 있습니다.

이 문제를 방지하려면 부분적인 장애를 처리할 수 있도록 서비스를 설계하는 것이 중요합니다.

따라야 할 좋은 접근 방식은 Netflix에서 설명한 방식입니다. 부분 장애에 대처하기 위한 전략은 다음과 같습니다:

  • 네트워크 시간 초과 – 응답을 기다릴 때 무기한 차단되지 않도록 항상 시간 초과를 설정하세요. 시간 초과를 사용하면 리소스가 무기한 점유되는 것을 방지할 수 있습니다.
  • 미처리 요청 수 제한 – 클라이언트가 특정 서비스에 보낼 수 있는 미처리 요청 수에 상한을 설정합니다. 한도에 도달한 경우 추가 요청을 보내는 것은 의미가 없을 가능성이 높으므로 해당 요청은 즉시 실패하도록 해야 합니다.
  • 회로 차단기 패턴 – 성공한 요청과 실패한 요청의 수를 추적합니다. 오류율이 설정된 임계값을 초과하면 회로 차단기를 작동시켜 이후의 시도가 즉시 실패하도록 합니다. 많은 요청이 실패한다면 서비스가 사용 불가능한 상태이며 요청을 보내도 소용없다는 의미입니다. 제한 시간이 지나면 클라이언트가 다시 시도하고, 성공하면 회로 차단기를 닫아야 합니다.
  • 대체 수단 제공 – 요청이 실패하면 대체 로직을 수행합니다. 예를 들어 캐시된 데이터나 빈 추천 목록과 같은 기본값을 반환합니다.

Netflix Hystrix는 이러한 패턴과 기타 패턴을 구현하는 오픈 소스 라이브러리입니다. JVM을 사용하고 있다면 Hystrix 사용을 적극적으로 고려해야 합니다. 또한 비 JVM 환경에서 실행 중이라면 이에 상응하는 라이브러리를 사용해야 합니다.

IPC 기술

선택할 수 있는 IPC 기술은 매우 다양합니다. 서비스는 HTTP 기반 REST나 Thrift와 같은 동기식 요청/응답 기반 통신 메커니즘을 사용할 수 있습니다. 또는 AMQP나 STOMP와 같은 비동기식 메시지 기반 통신 메커니즘을 사용할 수도 있습니다. 메시지 형식 역시 다양합니다. 서비스는 JSON이나 XML처럼 사람이 읽을 수 있는 텍스트 기반 형식을 사용할 수 있습니다. 또는 Avro나 Protocol Buffers처럼 더 효율적인 바이너리 형식을 사용할 수도 있습니다. 동기식 IPC 메커니즘은 나중에 살펴보고, 먼저 비동기식 IPC 메커니즘에 대해 알아보겠습니다.

비동기 메시지 기반 통신

메시징을 사용할 때 프로세스는 메시지를 비동기적으로 교환하여 통신합니다. 클라이언트는 서비스에 메시지를 보내 요청합니다. 서비스가 응답해야 하는 경우에는 별도의 메시지를 클라이언트에 다시 보냅니다. 통신이 비동기식이므로 클라이언트는 응답을 기다리는 동안 차단되지 않습니다. 대신 응답이 즉시 수신되지 않을 것을 전제로 클라이언트를 작성합니다.

A 메시지는 헤더(발신자 등의 메타데이터)와 메시지 본문으로 구성됩니다. 메시지는 채널을 통해 교환됩니다. 여러 생산자가 하나의 채널로 메시지를 보낼 수 있습니다. 마찬가지로 여러 소비자가 하나의 채널에서 메시지를 받을 수 있습니다. 채널에는 지점 간 채널과 게시-구독 채널의 두 종류가 있습니다. 지점 간 채널은 해당 채널에서 메시지를 읽는 소비자 중 정확히 하나에게 메시지를 전달합니다. 서비스는 앞서 설명한 일대일 상호 작용 방식에 지점 간 채널을 사용합니다. 게시-구독 채널은 연결된 모든 소비자에게 각 메시지를 전달합니다. 서비스는 위에서 설명한 일대다 상호 작용 방식에 게시-구독 채널을 사용합니다.

다음 다이어그램은 택시 호출 애플리케이션에서 발행-구독 채널을 어떻게 사용할 수 있는지 보여 줍니다.


택시 호출 애플리케이션의 마이크로서비스는 디스패처와 다른 서비스 간 통신에 게시-구독 채널을 사용합니다

Trip Management 서비스는 게시-구독 채널에 Trip Created 메시지를 기록하여 Dispatcher와 같은 관련 서비스에 새로운 Trip이 생성되었음을 알립니다. Dispatcher는 배정 가능한 운전자를 찾은 후 게시-구독 채널에 Driver Proposed 메시지를 기록하여 다른 서비스에 알립니다.

선택할 수 있는 메시징 시스템은 많습니다. 다양한 프로그래밍 언어를 지원하는 시스템을 선택해야 합니다. 일부 메시징 시스템은 AMQP 및 STOMP와 같은 표준 프로토콜을 지원합니다. 다른 메시징 시스템은 독자적이지만 문서화된 프로토콜을 사용합니다. 선택할 수 있는 오픈 소스 메시징 시스템도 매우 많으며, 여기에는 RabbitMQ, Apache Kafka, Apache ActiveMQ, NSQ 등이 있습니다. 개괄적으로 보면 이들은 모두 일정한 형태의 메시지와 채널을 지원합니다. 또한 모두 안정성, 고성능 및 확장성을 제공하기 위해 노력합니다. 그러나 각 브로커의 메시징 모델은 세부적인 면에서 상당한 차이가 있습니다.

메시징을 사용하면 다음과 같은 많은 이점이 있습니다:

  • 클라이언트와 서비스의 결합을 분리합니다. 클라이언트는 적절한 채널에 메시지를 보내는 것만으로 요청할 수 있습니다. 클라이언트는 서비스 인스턴스에 대해 전혀 알 필요가 없으며, 서비스 인스턴스의 위치를 확인하기 위해 검색 메커니즘을 사용할 필요도 없습니다.
  • 메시지 버퍼링 – HTTP와 같은 동기식 요청/응답 프로토콜에서는 통신이 이루어지는 동안 클라이언트와 서비스가 모두 사용 가능한 상태여야 합니다. 반면 메시지 브로커는 채널에 기록된 메시지를 소비자가 처리할 수 있을 때까지 대기열에 보관합니다. 따라서 예를 들어 주문 처리 시스템이 느리거나 사용할 수 없는 경우에도 온라인 상점은 고객의 주문을 받을 수 있습니다. 주문 메시지는 대기열에 쌓이기만 합니다.
  • 유연한 클라이언트-서비스 상호작용 – 메시징은 앞서 설명한 모든 상호작용 방식을 지원합니다.
  • 명시적 프로세스 간 통신 – RPC 기반 메커니즘은 원격 서비스 호출을 로컬 서비스 호출과 동일하게 보이도록 만들려고 합니다. 하지만 물리 법칙과 부분 장애의 가능성 때문에 실제로는 상당히 다릅니다. 메시징은 이러한 차이를 매우 명확하게 드러내므로 개발자가 잘못된 안전감에 빠지지 않도록 합니다.

하지만 메시징을 사용하는 데에는 몇 가지 단점도 있습니다:

  • 운영 복잡성 증가 – 메시징 시스템은 설치, 구성 및 운영해야 하는 또 하나의 시스템 구성 요소입니다. 메시지 브로커는 반드시 고가용성을 갖춰야 하며, 그렇지 않으면 시스템 안정성에 영향을 미칩니다.
  • 요청/응답 기반 상호 작용 구현의 복잡성 – 요청/응답 방식의 상호 작용을 구현하려면 어느 정도 작업이 필요합니다. 각 요청 메시지에는 응답 채널 식별자와 상관관계 식별자가 포함되어야 합니다. 서비스는 상관관계 ID가 포함된 응답 메시지를 응답 채널에 기록합니다. 클라이언트는 상관관계 ID를 사용하여 응답을 요청과 연결합니다. 요청/응답을 직접 지원하는 IPC 메커니즘을 사용하는 편이 더 쉬운 경우가 많습니다.

지금까지 메시징 기반 IPC를 사용하는 방법을 살펴보았으니, 이제 요청/응답 기반 IPC를 살펴보겠습니다.

동기식 요청/응답 IPC

동기식 요청/응답 기반 IPC 메커니즘을 사용할 경우 클라이언트는 서비스에 요청을 보냅니다. 서비스는 요청을 처리한 후 응답을 반환합니다. 많은 클라이언트에서는 요청을 보낸 스레드가 응답을 기다리는 동안 차단됩니다. 다른 클라이언트에서는 Futures 또는 Rx Observables로 캡슐화된 비동기 이벤트 기반 클라이언트 코드를 사용할 수도 있습니다. 하지만 메시징을 사용할 때와 달리, 클라이언트는 응답이 적시에 도착할 것이라고 가정합니다. 선택할 수 있는 프로토콜은 매우 다양합니다. 널리 사용되는 프로토콜로는 REST와 Thrift가 있습니다. 먼저 REST부터 살펴보겠습니다.

REST

오늘날에는 API를 RESTful 스타일로 개발하는 것이 일반적입니다. REST는 (거의 항상) HTTP를 사용하는 IPC 메커니즘입니다. REST의 핵심 개념은 리소스입니다. 리소스는 일반적으로 고객이나 제품과 같은 비즈니스 객체 또는 비즈니스 객체의 컬렉션을 나타냅니다. REST는 URL로 참조되는 리소스를 조작하기 위해 HTTP 동사를 사용합니다. 예를 들어 GET 요청은 리소스의 표현을 반환하며, 이는 XML 문서나 JSON 객체 형식일 수 있습니다. POST 요청은 새 리소스를 생성하고, PUT 요청은 리소스를 업데이트합니다. REST를 만든 로이 필딩의 말을 인용하면 다음과 같습니다:

“REST는 전체적으로 적용할 경우 컴포넌트 간 상호작용의 확장성, 인터페이스의 범용성, 컴포넌트의 독립적 배포, 그리고 상호작용 지연을 줄이고 보안을 강화하며 레거시 시스템을 캡슐화하는 중개 컴포넌트를 중시하는 일련의 아키텍처 제약 조건을 제공한다.”

다음 다이어그램은 택시 호출 애플리케이션에서 REST를 사용할 수 있는 방법 중 하나를 보여 줍니다.


마이크로서비스 기반 택시 호출 앱에서 승객의 스마트폰이 POST 요청을 보내면, 운행 관리 마이크로서비스가 이를 승객 확인 마이크로서비스에 대한 GET 요청으로 변환합니다.

승객의 스마트폰은 Trip Management 서비스의 POST 리소스에 /trips 요청을 보내 운행을 요청합니다. 이 서비스는 Passenger Management 서비스에 승객 정보를 요청하는 GET 요청을 보내 해당 요청을 처리합니다. 승객에게 운행을 생성할 권한이 있는지 확인한 후 Trip Management 서비스는 운행을 생성하고 스마트폰에 201 응답을 반환합니다.

많은 개발자가 자신이 만든 HTTP 기반 API가 RESTful하다고 주장합니다. 하지만 Fielding이 이 블로그 게시물에서 설명하듯이 실제로는 모두 그렇지는 않습니다. Leonard Richardson(저자와는 관계없음)은 다음 단계로 구성된 매우 유용한 REST 성숙도 모델을 정의했습니다.

  • 레벨 0 – 레벨 0 API의 클라이언트는 유일한 URL 엔드포인트에 HTTP POST 요청을 보내 서비스를 호출합니다. 각 요청에는 수행할 작업, 작업 대상(예: 비즈니스 객체), 그리고 필요한 매개변수가 지정됩니다.
  • 레벨 1 – 레벨 1 API는 리소스라는 개념을 지원합니다. 리소스에 대한 작업을 수행하기 위해 클라이언트는 수행할 작업과 매개변수를 지정하는 POST 요청을 보냅니다.
  • 레벨 2 – 레벨 2 API는 작업을 수행하기 위해 HTTP 동사를 사용합니다. 즉, GET은 조회에, POST는 생성에, PUT은 업데이트에 사용됩니다. 요청의 쿼리 매개변수와 본문(있는 경우)은 작업의 매개변수를 지정합니다. 이를 통해 서비스는 GET 요청 캐싱과 같은 웹 인프라를 활용할 수 있습니다.
  • 레벨 3 – 레벨 3 API의 설계는 이름이 형편없는 HATEOAS(Hypertext As The Engine Of Application State) 원칙을 기반으로 합니다. 기본 개념은 GET 요청이 반환하는 리소스 표현에 해당 리소스에서 허용되는 작업을 수행할 수 있는 링크가 포함된다는 것입니다. 예를 들어 클라이언트는 주문을 조회하기 위해 보낸 GET 요청에 대한 응답으로 반환된 주문 표현의 링크를 사용하여 주문을 취소할 수 있습니다. HATEOAS의 이점으로는 클라이언트 코드에 URL을 더 이상 하드코딩할 필요가 없다는 점이 있습니다. 또 다른 이점은 리소스 표현에 허용되는 작업의 링크가 포함되므로 클라이언트가 리소스의 현재 상태에서 어떤 작업을 수행할 수 있는지 추측할 필요가 없다는 것입니다.

HTTP 기반 프로토콜을 사용하면 다음과 같은 많은 이점이 있습니다:

  • HTTP는 간단하고 익숙합니다.
  • 브라우저에서 Postman과 같은 확장 프로그램을 사용하거나 명령줄에서 curl을 사용하여 HTTP API를 테스트할 수 있습니다(JSON 또는 기타 텍스트 형식을 사용한다고 가정).
  • 요청/응답 방식의 통신을 직접 지원합니다.
  • 물론 HTTP는 방화벽과도 잘 호환됩니다.
  • 중간 브로커가 필요하지 않아 시스템 아키텍처가 단순해집니다.

HTTP를 사용하는 데에는 몇 가지 단점이 있습니다:

  • 요청/응답 방식의 상호작용만 직접 지원합니다. 알림에 HTTP를 사용할 수는 있지만, 서버는 항상 HTTP 응답을 보내야 합니다.
  • 클라이언트와 서비스가 메시지를 버퍼링하는 중개자 없이 직접 통신하므로, 통신이 이루어지는 동안에는 둘 다 실행 중이어야 합니다.
  • 클라이언트는 각 서비스 인스턴스의 위치(즉, URL)를 알고 있어야 합니다. API 게이트웨이에 관한 이전 글에서 설명했듯이, 이는 현대적인 애플리케이션에서 해결하기 쉽지 않은 문제입니다. 클라이언트는 서비스 검색 메커니즘을 사용하여 서비스 인스턴스를 찾아야 합니다.

최근 개발자 커뮤니티에서는 RESTful API를 위한 인터페이스 정의 언어의 가치를 재발견하고 있습니다. 선택지로는 RAML과 Swagger 등이 있습니다. Swagger와 같은 일부 IDL을 사용하면 요청 및 응답 메시지의 형식을 정의할 수 있습니다. RAML과 같은 다른 IDL은 JSON Schema와 같은 별도의 명세를 사용해야 합니다. IDL에는 API를 기술하는 기능뿐만 아니라 인터페이스 정의를 바탕으로 클라이언트 스텁과 서버 스켈레톤을 생성하는 도구도 일반적으로 포함되어 있습니다.

Thrift

Apache Thrift는 REST를 대체할 수 있는 흥미로운 대안입니다. 여러 언어에서 사용할 수 있는 RPC 클라이언트와 서버를 작성하기 위한 프레임워크입니다. Thrift는 API를 정의할 수 있도록 C 스타일의 IDL을 제공합니다. Thrift 컴파일러를 사용하여 클라이언트 측 스텁과 서버 측 스켈레톤을 생성할 수 있습니다. 이 컴파일러는 C++, Java, Python, PHP, Ruby, Erlang, Node.js를 비롯한 다양한 언어의 코드를 생성합니다.

Thrift 인터페이스는 하나 이상의 서비스로 구성됩니다. 서비스 정의는 Java 인터페이스와 유사하며, 강력한 형식이 지정된 메서드의 모음입니다. Thrift 메서드는 값(void일 수도 있음)을 반환하거나 단방향으로 정의할 수 있습니다. 값을 반환하는 메서드는 요청/응답 방식의 상호 작용을 구현합니다. 클라이언트는 응답을 기다리며 예외를 발생시킬 수도 있습니다. 단방향 메서드는 알림 방식의 상호 작용에 해당합니다. 서버는 응답을 보내지 않습니다.

Thrift는 JSON, 바이너리, 컴팩트 바이너리 등 다양한 메시지 형식을 지원합니다. 바이너리는 디코딩 속도가 더 빠르므로 JSON보다 효율적입니다. 또한 이름에서 알 수 있듯이 컴팩트 바이너리는 공간 효율적인 형식입니다. 물론 JSON은 사람과 브라우저가 다루기 편리합니다. Thrift에서는 원시 TCP와 HTTP를 비롯한 전송 프로토콜도 선택할 수 있습니다. 원시 TCP는 HTTP보다 효율적일 가능성이 높습니다. 하지만 HTTP는 방화벽, 브라우저 및 사용자 친화적입니다.

메시지 형식

지금까지 HTTP와 Thrift를 살펴보았으니, 이제 메시지 형식에 대해 알아보겠습니다. 메시징 시스템이나 REST를 사용한다면 메시지 형식을 직접 선택할 수 있습니다. Thrift와 같은 다른 IPC 메커니즘은 소수의 메시지 형식만 지원하거나, 어쩌면 단 하나만 지원할 수도 있습니다. 어느 경우든 여러 언어에서 사용할 수 있는 메시지 형식을 채택하는 것이 중요합니다. 현재는 단일 언어로 마이크로서비스를 작성하고 있더라도, 앞으로는 다른 언어를 사용하게 될 가능성이 높기 때문입니다.

메시지 형식은 크게 텍스트와 바이너리, 두 가지로 나뉩니다. 텍스트 기반 형식의 예로는 JSON과 XML이 있습니다. 이러한 형식은 사람이 읽을 수 있을 뿐만 아니라 자체 설명적이라는 장점이 있습니다. JSON에서는 객체의 속성을 이름-값 쌍의 모음으로 표현합니다. 마찬가지로 XML에서는 속성을 이름이 지정된 요소와 값으로 표현합니다. 따라서 메시지 소비자는 자신에게 필요한 값만 골라내고 나머지는 무시할 수 있습니다. 그 결과 메시지 형식이 조금 변경되더라도 이전 버전과의 호환성을 쉽게 유지할 수 있습니다.

XML 문서의 구조는 XML 스키마로 정의됩니다. 시간이 지나면서 개발자 커뮤니티는 JSON에도 이와 유사한 메커니즘이 필요하다는 사실을 깨닫게 되었습니다. 한 가지 방법은 JSON 스키마를 단독으로 사용하거나 Swagger와 같은 IDL의 일부로 사용하는 것입니다.

텍스트 기반 메시지 형식을 사용할 때의 단점은 메시지가 장황해지는 경향이 있다는 점이며, 특히 XML이 그렇습니다. 메시지가 자체 설명적이므로 모든 메시지에는 특성 값뿐만 아니라 특성 이름도 포함됩니다. 텍스트 구문 분석에 따른 오버헤드도 또 다른 단점입니다. 따라서 이진 형식 사용을 고려할 수 있습니다.

선택할 수 있는 바이너리 형식은 여러 가지입니다. Thrift RPC를 사용한다면 바이너리 Thrift를 사용할 수 있습니다. 메시지 형식을 직접 선택할 수 있다면 널리 사용되는 옵션으로는 Protocol Buffers와 Apache Avro가 있습니다. 두 형식 모두 메시지 구조를 정의하기 위한 형식 지정 IDL을 제공합니다. 하지만 Protocol Buffers는 태그가 지정된 필드를 사용하는 반면, Avro 소비자는 메시지를 해석하려면 스키마를 알아야 한다는 차이가 있습니다. 따라서 API를 발전시키기는 Avro보다 Protocol Buffers가 더 쉽습니다. 이 블로그 게시물에서는 Thrift, Protocol Buffers, Avro를 훌륭하게 비교합니다.

요약

마이크로서비스는 프로세스 간 통신 메커니즘을 사용하여 통신해야 합니다. 서비스 간 통신 방식을 설계할 때는 서비스가 상호 작용하는 방식, 각 서비스의 API를 정의하는 방법, API를 발전시키는 방법, 부분 장애를 처리하는 방법 등 다양한 문제를 고려해야 합니다. 마이크로서비스에서 사용할 수 있는 IPC 메커니즘에는 비동기 메시징과 동기식 요청/응답이라는 두 가지 유형이 있습니다. 이 시리즈의 다음 글에서는 마이크로서비스 아키텍처의 서비스 디스커버리 문제를 살펴보겠습니다.

편집자 – 총 7부로 구성된 이 연재 기사가 이제 완결되었습니다:

  1. 마이크로서비스 소개
  2. 마이크로서비스 구축: API 게이트웨이 사용
  3. 마이크로서비스 구축: 마이크로서비스 아키텍처의 프로세스 간 통신(이 글)
  4. 마이크로서비스 아키텍처의 서비스 디스커버리
  5. 마이크로서비스를 위한 이벤트 기반 데이터 관리
  6. 마이크로서비스 배포 전략 선택
  7. 모놀리스를 마이크로서비스로 리팩터링하기

전체 기사 모음과 NGINX Plus를 사용한 마이크로서비스 구현 정보를 전자책으로 다운로드할 수도 있습니다. 전자책 제목은 마이크로서비스: 설계부터 배포까지입니다.

객원 블로거 Chris Richardson은 최초의 CloudFoundry.com을 설립했습니다. 이 서비스는 Amazon EC2용 초기 Java PaaS(서비스형 플랫폼)였습니다. 현재 그는 여러 조직을 대상으로 애플리케이션 개발 및 배포 방식을 개선할 수 있도록 컨설팅하고 있습니다. 또한 http://microservices.io에서 마이크로서비스에 관한 글을 정기적으로 게시합니다.


공유

관련 블로그 게시물

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
NGINX | 2023. 11. 30.

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric

이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
NGINX | 2023. 03. 23.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법

앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
NGINX | 2021. 09. 27.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션

개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX | 2021. 07. 06.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법

NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX | 2021. 06. 30.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리

NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
NGINX | 2015. 03. 25.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유

Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.