1. MSA?

Micro Service Architecture. 쉽게 접근해보기로 했다. 

"하나의 큰 애플리케이션을 여러 개의 작은 애플리케이션으로 쪼개어 변경과 조합이 가능하도록 만든 아키텍처" 라고 한다.

처음에는 음? 뭔가 새로운 개념인건가 싶었지만, 다양한 용어로 이름 붙여져서 왔을 뿐, 갑자기 등장한 개념은 아니라는거. 많은 기업들에서는 이미 이런 방식으로 서비스를 분리하여 애플리케이션을 만들어왔다고 하는데.

왜 이런 움직임들이 생겨나게 된 것일까? 무언가 그들이 원하는 서비스의 방향이 전통적인 아키텍처에서는 어려움이 있었다는 건데..


2. 모노리틱 아키텍처

그럼, 먼저 전통적인 아키텍처. 모노리틱 아키텍처 (Monolithic Architecture)에 대해 이야기를 꺼내야 할 것 같다. (혹자는 모노리식 아키텍처라고 부른다. 뭐가 맞는건지?)

전통적인 SI의 모노리틱 아키텍처는 여러 기능을 패키지 단위로 구분하여 하나의 프로젝트로 관리하였다. 만약 다른 기능의 서비스나 데이터가 필요할 때는 클래스 단위로 참조하거나 혹은 DB 데이터를 join하던지 하였다.

2.1. 모노리틱 아키텍처의 단점

  • 코드가 점점 증가한다. 그리고 화면 개발자, 로직 개발자가 모두 같은 코드 저장소에 커밋한다.
  • 간단한 변경 하나도 모두 리패키징하고 다시 배포해야 한다.
  • 결국 개별 개발자가 어느 정도 프로젝트 전체 시스템을 이해하고 있어야 한다.
  • 다른 팀(기능)에서 오류난 소스를 커밋하면 빌드 오류로 인하여 우리 팀의 소스가 반영되지 않을 수 있다.
  • 클래스 참조가 늘어나면 서비스간 의존성이 늘어나 수정이 어려워진다. (Tight Coupled)
  • 프로젝트가 커지면 빌드와 배포에 많은 시간이 소요된다.
  • 특정 기능에 부하가 몰려도 전체 서비스 단위로 늘려야 한다.
  • 프로젝트가 클수록 협업이 어려워진다.
  • 문제가 발생 시 각 계층 간에 비난이 일 수 있다.

3. MSA

MSA는 개념적으로는 완전 새로운 것은 아니다. 예전 CBD나 SOA와 개념적으로는 유사한 점이 있다. 자바 기반이라면 스프링 부트를 많이 사용하고 DB 처리를 위해서는 JPA를 사용하기도 한다. 또 도커 등 플랫폼에 따라 각 기능을 위한 IP 포트가 동적으로 할당되는 경우가 많기 때문에 서비스 등록 및 관리를 위한 Eureka 등의 Service Discovery 패턴이 등장했다.

각 기능별로 주소가 다르기 때문에 API 호출이 쉽지 않은데 단일 엔드포인트 제공을 위해 API Gateway 패턴을 사용하기도 한다. MSA에서 API Gateway는 서비스 요청에 대한 진입점 역할을 한다.APIM(WSO2), Zuul(Netflix) 등이 그 예이다. Zuul은 Java(JVM) 기반이다.

참고로 Netflix는 1억 1,800만 명의 가입자에게 1일 1억 이상의 비디오 스트리밍을 제공하고 있다. 그리고 높은 재생 품질을 다운타임 없이 서비스하는 것을 목표로 마이크로 서비스 아키텍처를 적용하였다. 마이크로 서비스에 적합한 조직, 클라우드 기반 서비스 생명주기 관리 등을 고려하였으며, 이를 위해 Service Discovery, Circuit Breaker, API Gateway 등의 기술을 도입하였다. 또한, Netflix OSS를 통해, 기술의 확장 및 외부 커뮤니티와의 지속적 커뮤니케이션을 진행 중이다.

3.1. MSA의 장점

  • 모노리틱 애플리케이션은 단일 언어로 개발하지만 MSA의 각 서비스는 독립적이기 때문에 자바, 노드 등 어떤 언어를 사용해도 무방하다.
  • 중앙 데이터베이스가 없이 개별 기능이 RDBMS 혹은 NoSQL을 독자적으로 가진다. DB는 기능 특성에 따라 선택한다.
  • 각 기능이 서로 다른 데이터 스토리지 기능을 사용할 수도 있다.
  • 내부적으로 어떻게 되어 있든 각 기능들을 REST API로 노출하기 때문에 다른 서비스와 통신할 때는 REST API를 통해 통신한다.
  • SOA의 ESB는 데이터를 관리, 변환, 분류, 라우팅 등 많은 역할을 가지고 있지만 MSA는 엔드포인트 기반의 단순한 메시지 버스 구조를 가진다.
  • 기능별로 따로 개발하고 각각의 리포지토리를 갖기 때문에 빌드와 배포 시간이 짧다.
  • 소프트웨어 구조와 조직 구조가 일치할 수 있다.

3.2. MSA의 단점

  • 프로젝트를 구성하는 많은 서비스가 있다보니 모니터링이 어렵다.
  • 장애가 발생하면 장애 추적이 어렵다.
  • 서비스 간 호출 경로 추적이 어렵다.
  • 각 서비스가 로그를 생성하기 때문에 중앙 로그 관리, 모니터링이 어렵다. 정교한 로그 관리 시스템이 필요하다.
  • 모노리틱 아키텍처의 내부 통신에 비해 API 원격 호출 시 네트워크 전송 오버헤드가 발생한다. 성능 이슈.
  • 공통 기능에 대한 중복이 발생하여 자원 사용이 비효율적이다.
  • 다양한 기술을 사용하기 때문에 운영이 어렵다.
  • 트랜잭션 구현이 어렵다. (분산 트랜잭션 리스크 제거를 위해 통합 DB를 사용할 수도 있다)
  • JOIN이 어렵다. 굉장히 큰 장벽인데, 처음에는 READ성이라는 이유로 MSA로 분리하였으나 JOIN을 극복하지 못하여 다시 합치는 경우도 있다.

3.3. MSA의 트랜잭션 처리 방안

  • 분산 트랜잭션 자체를 제거
  • 금융, 제조 등 엔터프라이즈 시스템은 모노리틱 아키텍처로 접근,  MSA는 아무래도 B2C에 적합
  • 보상 트랜잭션, 예외 처리 로직을 DB가 아닌 애플리케이션 단에서 처리
  • 다만 투자 대비 효과를 볼 필요가 있음, 무슨 말인가하면 많지 않은 건의 완벽한 분산 트랜잭션을 위해 너무 많은 노력이 든다면, 차라리 로그(audit)를 통해 문제가 생길 때 audit 기반으로 대응하는 것이 효과적일 수도 있음 

보상 트랜잭션의 예는 다음과 같다.

  • 주문 서비스 : 상품 주문 등록 -> 상품 주문 실패
  • 결제 서비스 : 신용카드 승인 -> 신용카드 승인 취소
  • 물류 서비스 : 재고차감 -> 재고수량 변경

타행이체 시나리오를 확인해보자.

  1. <계좌조회 API>, <출금 API> 호출, 타행이체정보를 DB에 저장
  2. <타행이체> Publish
  3. <은행간이체 서비스>는 <타행이체>를 Subscribe하고 처리하여 <처리결과>(성공/실패)를 Publish
  4. <타행이체결과등록>은  <처리결과>를 Subscribe하여 성공/실패를 등록함
  5. 만약 처리결과가 실패이면 <출금취소 API> 호출하여 처리

3.4. 관련 프레임워크

3.4.1. Eventuate 프레임워크

오픈소스 프레임워크로 MSA의 분산 데이터 관리 문제를 해결하는 것이 목표이다. 세계적 권위자인 크리스 리차드슨이 개발한다.

마이크로 서비스간 데이터 분산을 해결하기 위해 CDC나 Saga 패턴을 지원한다. 

  • Eventuate Tram (TRAnsaction Message)
    - 전통적인 JPA/JDBC 기반 MSA를 위한 것이다. 비즈니스 로직 수정 없이 스프링 기반 MSA에 적용 가능하다.
    - Eventuate Tram(Core)는 Choreography 기반 Saga를 지원한다.
    - Eventuate Tram(Saga)는 Orchestration 기반 Saga를 지원한다.
  • Eventuate Local
    - Event 저장소의 구현체이다. 저장소는 MySQL 등을 사용한다.
    - 애플리케이션은 기본 키로 Aggregate Event를 삽입/검색한다.
    - 멀티 Language를 지원하며 이벤트 소싱 기반이다. 데이터 변경 시 이벤트의 퍼블리싱, 업데이트에 대한 감사 등을 제공한다.

4. MSA의 의미

MSA는 물리적으로 분리된다. 이 물리적인 분리가 1) 비즈니스적으로, 2) 기술적으로 어떠한 파급효과가 있는지, 무엇이 달라지는지 명확히 파악할 필요가 있다. 

예를 들어 기술적으로는 기존에 없던 새로운 문제들이 발생한다. 인스턴스 수가 엄청 증가하며, 효율적으로 정확한 배포방안이 필요하다.

MSA를 왜 하느냐? 사실 이것은 간단하다. Business Agility를 위해서다. 그런데 Agility가 그렇게 중요한 Business가 아니라면? 또 MSA를 통한 득보다 실이 많다면? MSA를 할 필요가 없다고 생각한다. 다만 신규 프로젝트 시에는 가급적 분리를 하는 것이 (= 쪼개는 것이) 좋다. 시간이 대부분 지나면 시스템은 커지기 때문이다.


5. Architecture

5.1. Inner Architecture

5.1.1. 개념

응용 레벨의 아키텍처이다. 모델링, 테스트 등이 여기에 해당한다,

아마존의 예를 들어보면 기능 목록은 다음과 같다.

  • 상품 정보
  • 쇼핑 카트에 담긴 상품 수
  • 주문 내역
  • 고객 리뷰
  • 수량이 얼마 안남았으면 경고 보여주기
  • 배송 옵션
  • 다른 사람들이 해당 상품과 같이 구매한 상품 추천
  • 구매 옵션

과거의 모놀리식 방식이라면 이러한 데이터를 일련의 과정을 통해 한번에 가져올 테지만 마이크로 서비스화되어 있다면 다음과 같을 것이다.

  • 상품 정보, 구매 옵션 -> Catalog Service
  • 쇼핑 카트에 담긴 상품 수 -> Shopping Cart Service
  • 주문 내역 -> Order Service
  • 고객 리뷰 -> Review Service
  • 수량이 얼마 안남았으면 경고 보여주기 -> Inventory Service
  • 배송 옵션 -> Shipping Service
  • 다른 사람들이 해당 상품과 같이 구매한 상품 추천 -> Recommendation Service

eBay는 Catalog, List, Offer, Cart, Shipping .. 등으로 나눴다고 한다.

이러한 온라인 쇼핑몰에 마이크로 서비스가 적합한 이유는 예를 들어 지불 방법에 대한 변경이 필요한 경우 지불용 마이크로 서비스에만 변경 사항을 적용, 배치하면 되기 때문이다. (당연한 이야기)

5.1.2. DB 분산

  • n개의 DB로 분산할 것인지 정한다.
  • 서비스별로 분리, 예) 상품/전시/주문/쿠폰 등으로 분리하거나, 상품/가입/이벤트 등으로 분리한다.
  • 조회용 데이터는 복제용 DB를 구성해서 사용 가능, 복제 방식은 EAI 등
  • DB 동기화 방안 고민할 때 Kafka 등도 고민할 수 있다. 데이터 변경 시 Kafka에 저장하고 필요한 곳에서 Kafka에서 가져간 후 알아서 사용한다.

5.1.3. 배치

배치도 어떻게 마이크로 서비스화할 것인지 고민해야 한다.

5.2. Outer Architecture

Inner를 감싸고 있는 아키텍처이다.

5.1.의 예를 보면 단점도 보이는데 네트워크 트래픽이 많아지고 HTTP 방식이 아니라면 클라이언트에서의 호출이 쉽지 않다.

5.2.1. 단계별 절차

  1. Outter Architecture 설계 : 요구사항 / 환경분석 → 솔루션 검토/설계 → 파일럿 개발 / 설계 표준
  2. Outer Architecture 개발 : 인터페이스설계/검토 → 분산 트랜잭션 패턴 설계 / 적용 → 인터페이스 / 트랜잭션 검증 및 안정화

5.2.2. Cache Solution

세션 처리를 위하여 Redis 도입을 고려할 수 있다.

5.2.3. 메시지 

5.3. DevOps + SLA

5.4. 테스트

5.4.1. API 서버 사이의 테스트

Junit 기반 테스트하면서 DB는 무엇을 사용할 지 정한다. (예: 개발DB)

5.5. 구현 예시

  1. UI 및 외부 시스템에서는 API Gateway를 통해 서비스 호출, 해당 서비스들을 Eureka에 등록된 서비스 명으로 접근한다.
  2. 동기 방식인 경우는 OpenFeign 기술 적용 (REST 방식)
  3. 비동기 방식인 경우는 Kafka를 통해 연계
  4. API Composition, CQRS 패턴 적용 등

6. 적용 방법론

어떤 대상에, 어떻게 MSA를 적용할 것인가에 대한 고민이다. 전부 다 MSA를 할 필요는 절대 없다. Benefit을 주는 것만 한다.

6.1. New Build

신규로 MSA를 시작한다.

6.2. Divide & Shift

점진적으로 MSA로 전환한다.

6.3. Add On

추가되는 비즈니스만 MSA로 시도한다.


7. API Gateway

API란? 다음은 예이다.

Application ->

  1. API Endpoint (메시지 전송) - API Backend (메시지 처리) - Data Layer (메시지 리파지토리)
  2. API Endpoint (사용자 정보 조회) - API Backend (유저 관리 로직) - Data Layer (유저 데이터베이스)

7.1. API Gateway  역할

  • API Gateway를 이용하면 회사 내부의 마이크로 서비스 간 연결뿐 아니라 외부와 연동시에도 노출할 수 있다.
  • 요청에 따라 필요한 서비스로 라우팅
  • 내부에서 사용하는 프로토콜이 다르면 웹 친화적 프로토콜(HTTP, WebSocket)로 변환 가능
  • 클라이언트와의 통신 감소, 클라이언트 코드 단순화
  • 권한 인증, 로드 밸런싱, 캐싱, 측정 등 가능
  • API Gateway 장애 시 서비스 전체 먹통, 따라서 API Gateway 자체도 관리 포인트임
  • 각 서비스 API 수정 시 API Gateway도 함께 수정해야 한다.

각 서비스 간에 사용자는 방식이 다르면 여러 방식을 지원해야 한다.

  • 비동기 메시지 방식 : JMS, AMQP
  • 동기 방식 : HTTP, Apache Thrift

7.2. AWS API Gateway

  • AWS를 사용한다면 제공되는 API Gateway 서비스를 사용할 수 있다.
  • 다만 비용에 대한 문제가 발생할 수 있다.
  • 대안으로 Zuul on EC2를 멀티로 구성하고 ELB를 배치하는 방안을 고려할 수 있다.
  • 또 AWS의 API Gateway는 초당 요청 한도 제한, 파일 업로드 용량 제한이 존재한다.

7.3. Spring Cloud Gateway (Zuul)

기존의 Reverse Proxy, Load Balancer는 특정 요청(URI)를 보낼 서버의 IP나 도메인에 대한 사전 정의가 필요하다. 제품에 따라 설정 변경에 따른 리부팅이 필요하고 일시적인 서비스 영향이 발생할 수도 있다.

Spring Cloud Gateway는 엔드포인트를 통일하고 적절한 서비스로 라우팅하는 기능을 제공하는 JVM 기반 Gateway다. 클라이언트 입장에서는 엔드포인트가 단순화되므로 연동 고려 사항이 줄어든다. 다만 Gateway 영역은 클라이언트의 최접점이므로 여러대로 구성한 후 Load Balancer로 묶어 가용성을 확보해야 한다.

CORS를 서비스로 라우팅할 수도 있다.

Config 서버와의 연동이 일반적이며 Config 서버와 연동하지 않는 경우 직접 라우팅할 서버의 목록을 적어서 관리한다. 

요청 처리를 위한 4가지 필터를 제공한다.

  • PRE : 트래픽이 업스트림(백엔드)으로 전송되기 전에 호출되는 필터다. 일반적으로 인증, 로깅, 디버깅 등을 처리한다.
  • ROUTE : 업스트림을 선택한 후 호출되며, 일반적으로 프록시 작업을 처리한다. HtrtpClient나 Ribbon 등을 사용한다.
  • POST : 업스트림이 응답을 반환하기 시작한 후 호출되며, 일반적으로 응답에 HTTP 헤더를 추가하거나 API 응답속도, 각종 메트릭 수집 용도이다.
  • ERROR : 필터 체인 처리 시 오류가 발생할 떄 호출되며, 일반적으로 최종 사용자에게 오류 메시지나 UI 표시를 위해 사용된다.

8. CQRS (Command and Query Responsibility Segregation)


9. 모니터링

9.1. Zipkin

일반적으로 Zipkin으로 trace하고 호출 관계를 파악한다.

Zipkin은 오버헤드가 거의 없으며 애플리케이션의 trace time을 비율(퍼센트) 형태로 볼 수 있다. Sleuth와 연계하여 사용한다.

9.2. Jaeger

우버에서 만든 분산현 추적 시스템이다. 오픈 소스이며 현재는 CNCF 프로젝트이다.

따라서 쿠버네티스 환경에 적합하다고 할 수 있다. (istio 같은 Proxy를 Jarger Supporter와 함께 사용하면 컨테이너 호출 추적이 용이함)

한편 Jaeger를 사용하는 경우 기존에 Zipkin을 사용하였다면 코드 수정이 필요 없다.


10. Strangler Pattern

마틴 파울러가 제시한 방법론으로, 레거시 시스템을 마이크로 서비스로 전환하려고 하는데 기존 서비스에 영향을 주지 않고 점진적으로 전환하는 방식이다. 가장 큰 목적은 "위험 감소"이다. 미국의 bestBuy가 Strangler Pattern을 적용하여 단계별로 교체를 했다고 한다. 참고로 Best Buy의 아키텍처는 2000년대 초 IIS+SQLServer로 시작하여 이후 Dynamo+Oracle로 운영 중이었다.

10.1. 방법

  1. 분리(전환)하려는 도메인을 서비스 레이어로 리팩토링한다.
  2. 분리한 도메인의 API가 도출되면 Proxy에서 라우팅을 돌린다.
  3. 신규 구축한 서버로 리다이렉트한다.

초기에는 분리가 용이한 기능부터 시작해보고 점점 난이도가 높은 것에 도전한다!

10.2. 고려해야 할 점

일정기간 레거시와 신 서비스간에 공생이 이루어져야 한다는 것이다. 기본 전략은 이벤트 가로채기인데, 즉 분리할 기능이 동작하는 이벤트를 가로채서 처리하고 레거시에 결과를 전달해야 하는 것이다.

메시지 큐 같은 것이 있다면 전달이 수월하지만 그렇지 않을 경우 테이블 업데이트 등을 구현해야 한다.


11. gRPC

gRPC가 무엇인지는 설명은 생략한다.

  • 조금만 알려준다면 구글 내부에서 마이크로 서비스간 연계 시 사용하던 프레임워크를 오픈한 것이다.
  • ProtoBuf의 IDL로 정의하면 고성능을 보장하는 서비스와 메시지에 대한 소스 코드가 생성된다.
  • 압축률이 좋아 네트워크 트래픽을 절약할 수 있다.

gRPC가 MSA에 적합한 이유는 다양한 언어와 플랫폼을 지원하는 Poloyglot이 가능하기 때문이다.

protoBuf가 지원하는 IDL를 활용한 메시지 정의는 다양한 기술 스택의 공존에 의한 중복 발생의 단점을 보완하고 많은 서비스간의 API 호출에 의한 성능 저하를 개선한다.

다만 REST API 위주의 시스템에는 적합치 않다. gRPC는 RPC이기 때문이다.


12. Saga Pattern


13. 인증 및 권한 처리

  • Sticky : 다른 서버로 들어가면 세션 없음
  • 세션 복제 : 인스턴스 증가하면 부하 증가함
  • 중앙식 세션 관리 : 세션 저장소에 대한 관리, 보호 매커니즘 필요
  • 클라이언트 토큰 : 보통 쿠키 형태로 저장, 암호화 필요
  • SSO : 한번만 로그인 하면 관련 시스템 추가 인증없이 사용 가능, 사영자와 SSO 서버의 상호 작용 필요, 인증을 위한 반복적 네트워크 트래픽 발생
  • API Gateway를 통한 클라이언트 토큰 : 암호화 필요

14. 테스트 전략

아키텍처를 구성하는 아주 작은 단위부터 각 서비스, 그리고 서비스들로 이루어진 시스템까지 전체적인 테스트가 필요하다. 기존 monolith 시스템에 비해 여러 서비스를 제어해야 하는 등 복잡도가 증가하여 MSA에서의 테스트는 쉽지 않다.

또한 신속하고 안정적인 테스트를 위해 테스트는 코드로 작성해야 한다.

14.1. 단위 테스트 (Unit Test)

메소드 단위부터 여러 메소드와 구성요소를 호출하는 단위까지 다루며, 서비스의 요구사항을 만족하는지 확인한다.

테스트가 단위로 이루어지기 때문에 여러 의존적인 요소는 배제하고 test double을 사용하여 테스트를 수행한다.

어느 부분에서 오류가 발생하는지 가장 빠르고 정확하게 알 수 있다.

14.2. 통합 테스트

서비스 내에서 호출하는 외부 서비스까지 포함하여 테스트한다.

외부와 통신, 상호작용을 위해서는 Mock을 사용한다.

14.3. E2E 테스트

연관된 모든 서비스를 테스트한다.

14.4. Contract 테스트

API, 메시지 등이 개발한대로 되는지 검증하는 테스트이다.

통합 테스트와 유사하나 타 마이크로서비스를 블랙박스로 취흡하고 응답형식만 검증한다.

14.5. 컴포넌트 테스트

외부와 단절 상태에서 마이크로서비스 전체를 테스트한다.

가상의 mock, stub이 필요하다.