본문 바로가기

도서/스프링으로하는 마이크로서비스 구축

마이크로서비스 아키텍처(MSA) 란?

개요

현재 이커머스 B2B 솔루션 서비스를 제공하고 있는 회사에 재직 중에 사내에서 기존의 레거시 시스템을 MSA로 구축을 위한 움직임을 조금씩 보이고 있는 것 같아 그 변화에 합류하기 위해 MSA 공부를 시작하게 됐습니다. 

 

현재의 거대한 레거시 시스템은 주문, 재고관리등 다양한 서비스들이 서로 얽혀 있는 스파게티 코드로써 동작하고 있어서 디버깅과 유지보수가 매우 어렵다는 명확한 단점이 존재 하고 있습니다. 각각의 서비스들이 단일 컴포넌트로써 동작하기를 누구보다 간절히 바라고 있는 사람으로서 MSA 공부의 필요성에 대해 느끼게 되었습니다. 

 


교재 선택 

교재는 스프링으로 하는 마이크로서비스 구축 을 선택했습니다. 사내에서 자바로 된 코드는 한 줄도 없지만 이 책을 선택한 이유는 자바에 대한 감을 잃고 싶지 않다는 이유가 가장 컸던 것 같습니다. 또한 목차를 보니 단원별로 어떤 목적을 갖고 학습을 해야 하는지가 다른 교재에 비해서 명확하다고 판단했습니다. 

 


미리 보기 

본 포스팅에 앞서 자주 등장하는 용어를 미리 정의 함으로써 내용의 이해를 도우려고 합니다. 

  • 독립 소프트웨어 컴포넌트 : 특정 서비스를 제공하는 하나의 단위입니다. 이 컴포넌트들은 독립적으로 동작하며, 서로 API를 통해 상호작용합니다. 각 컴포넌트는 독립적으로 배포되고 관리되며, 필요에 따라 여러 인스턴스로 나뉘어 배포될 수 있습니다.
  • 각 컴포넌트의 인스턴스들 : 하나의 컴포넌트가 여러 서버에 분산되어 실행되는 형태를 의미합니다. 예를 들어, 특정 컴포넌트가 3개의 서버에 배포되어 있다면, 이 컴포넌트는 3개의 인스턴스를 가진다고 할 수 있습니다. 이렇게 함으로써, 하나의 서버에 문제가 발생하더라도 다른 서버에서 서비스가 지속될 수 있어 높은 가용성을 보장할 수 있습니다. 
    예를 들어 클라우드에 배포된 주문 서비스를 담당하는 컴포넌트의 인스턴스들은 가상 서버를 통해 무한대로 확장 할 수 있습니다. 
  • scale out(스케일 아웃) : 서버의 가용성을 확장하는 것을 의미합니다. 이는 더 많은 서버를 추가하여 시스템의 처리 능력을 증가시키는 방법입니다. 예를 들어, 현재 2대의 서버에서 실행 중인 서비스를 4대로 늘리면, 이는 스케일 아웃을 통한 확장이라고 할 수 있습니다. 이 방법은 서버의 부하를 분산시키고, 서비스의 성능을 향상하며, 높은 트래픽을 처리할 수 있게 합니다.

위 그림에서 볼 수 있듯이, 컴포넌트는 여러 인스턴스로 나뉘어 배포될 수 있습니다. 이는 각 마이크로서비스를 스케일 아웃(scale out)하는 것을 의미합니다. 마이크로서비스 아키텍처에서 스케일 아웃은 중요한 특징으로, 이를 통해 시스템은 더 많은 트래픽을 처리할 수 있고, 서버의 부하를 분산시켜 서비스의 안정성과 가용성을 높일 수 있습니다.

 

또한, 각 마이크로서비스는 다른 마이크로서비스들과 상호작용하며, 필요한 데이터를 주고받거나 기능을 호출합니다. 예를 들어, 사용자 인증을 담당하는 마이크로서비스는 주문 처리 마이크로서비스와 통신하여 인증된 사용자만이 주문을 할 수 있도록 합니다. 이런 구조를 통해 각 마이크로서비스는 독립적으로 개발, 배포, 관리되면서도 전체 시스템의 일관성을 유지할 수 있습니다.

 

따라서 MSA에서 각 마이크로서비스는 독립적으로 동작하면서도, 서로 협력하여 하나의 통합된 시스템을 구성합니다. 이러한 구조는 시스템의 유연성과 확장성을 극대화하며, 빠른 배포와 안정적인 운영을 가능하게 합니다.

 


마이크로서비스의 장단점

장점

  1. 컴포넌트 대체 용이성
    • 각 컴포넌트는 독립적으로 설계되기 때문에, 특정 컴포넌트를 새로운 버전이나 다른 구현체로 쉽게 대체할 수 있습니다. 이는 시스템의 유지보수와 확장을 용이하게 합니다.
  2. 개별적 배포 및 업그레이드 가능
    • 마이크로서비스는 독립적으로 배포되고 업그레이드될 수 있습니다. 각 컴포넌트는 명확한 API를 사용하여 통신하기 때문에, 다른 컴포넌트와의 호환성을 유지하면서 변경을 적용할 수 있습니다. 이를 통해 시스템 전체를 중단하지 않고도 부분적인 개선이나 수정이 가능합니다.
  3. 스케일 아웃 용이
    • 명확한 API 사용은 각 컴포넌트를 여러 서버에 분산하여 스케일 아웃하는 것을 가능하게 합니다. 이를 통해 시스템은 더 많은 트래픽을 효율적으로 처리할 수 있으며, 성능과 가용성을 높일 수 있습니다.

단점

  1. 수동 로드 밸런싱 및 인스턴스 추가
    • 스케일 아웃 시, 수동으로 로드 밸런서를 설정하고 인스턴스를 추가해야 하는 경우, 휴먼 에러가 발생할 가능성이 있습니다. 이를 방지하기 위해 자동화된 도구와 스크립트를 사용하는 것이 필요합니다.
  2. 통신하는 컴포넌트 간 오류 전파
    • 통신하는 다른 컴포넌트에 오류가 발생하면, 연쇄적인 문제를 일으킬 수 있습니다. 플랫폼 내부 통신이 대부분 동기식이기 때문에, 한 컴포넌트의 중단은 다른 컴포넌트에 영향을 미칠 수 있습니다. 이를 해결하기 위해 회로 차단기 패턴(circuit breaker pattern)과 같은 전략을 도입할 수 있습니다.
  3. 일관성 유지의 어려움
    • 모든 컴포넌트 인스턴스의 구성을 일관성 있게 최신 상태로 유지하는 것은 어렵습니다. 이를 위해 지속적인 통합(CI)과 지속적인 배포(CD) 파이프라인을 사용하여 일관된 배포를 자동화할 필요가 있습니다.
  4. 하드웨어 리소스 모니터링의 복잡성
    • 여러 서버와 인스턴스에 걸쳐 분산된 시스템에서는 하드웨어 리소스의 모니터링이 복잡해집니다. 이를 해결하기 위해 중앙 집중식 모니터링 도구와 로그 수집 시스템을 사용하는 것이 필요합니다.
  5. 로깅 및 추적의 어려움
    • 각 마이크로서비스 내 컴포넌트의 로그 파일 간의 상호 연관성을 고려하는 것은 어려울 수 있습니다. 이를 해결하기 위해 분산 트레이싱(distributed tracing) 도구를 사용하여 요청의 흐름을 추적하고, 문제가 발생했을 때 원인을 쉽게 파악할 수 있습니다.

추가적인 고려사항

  • 데이터 일관성 관리
    • 마이크로서비스는 각자 독립적인 데이터베이스를 사용할 수 있기 때문에, 데이터 일관성을 유지하는 것이 어려울 수 있습니다. 이를 해결하기 위해 이벤트 소싱(event sourcing)이나 사가 패턴(saga pattern) 등의 기법을 사용할 수 있습니다.
  • 보안 관리
    • 각 마이크로서비스가 독립적으로 배포되기 때문에, 전체 시스템의 보안을 유지하는 것이 복잡할 수 있습니다. 이를 위해 서비스 간 인증 및 권한 부여를 위한 표준 프로토콜(예: OAuth2, JWT 등)을 사용하는 것이 필요합니다.

독립 컴포넌트의 업그레이드와 스케일링을 위한 필수 요구사항

  1. 데이터베이스 공유 금지
    • 각 컴포넌트는 독립적인 데이터베이스를 가져야 하며, 컴포넌트 간에 데이터베이스를 공유하지 않아야 합니다. 이를 통해 컴포넌트의 독립성을 보장하고 데이터 일관성 문제를 방지할 수 있습니다.
  2. 명확한 API 인터페이스 설계 및 구축
    • 컴포넌트 간의 통신은 명확하게 정의된 API를 통해 이루어져야 합니다. 이는 공조 마이크로서비스 간의 상호작용을 표준화하고, 각 컴포넌트가 독립적으로 변경되더라도 시스템의 일관성을 유지할 수 있도록 합니다.
  3. 개별적인 런타임 프로세스 배포
    • 각 컴포넌트는 독립적인 런타임 프로세스로 배포되어야 합니다. 예를 들어, 도커 컨테이너를 사용하여 각 컴포넌트를 인스턴스화하고 배포할 수 있습니다. 이는 컴포넌트의 격리와 독립적인 관리 및 스케일링을 가능하게 합니다.
  4. 상태 비저장(stateless) 컴포넌트
    • 마이크로서비스는 상태 비저장(stateless)이어야 합니다. 즉, 모든 마이크로서비스 인스턴스는 들어오는 요청을 독립적으로 처리할 수 있어야 합니다. 이는 스케일링과 장애 복구를 용이하게 합니다.

독립 컴포넌트의 문제점과 해결 방안

문제점

  • 예를 들어, 사용자의 주문 내역을 조회하기 위해 사용자 마이크로서비스가 주문 마이크로서비스를 호출하는 경우, 이 API 호출은 일반적으로 동기적입니다. 만약 주문 서비스에 시스템 오류가 발생하여 응답을 주지 못하면, 사용자 서비스는 타임아웃 에러가 발생할 때까지 대기하게 됩니다. 이는 고객의 시스템 만족도에 부정적인 영향을 미칠 수 있습니다.

해결 방안

  1. 오류 감지 및 복구
    • MSA는 각 마이크로서비스의 시스템 오류를 감지하고 고장난 컴포넌트를 자동으로 다시 시작할 수 있는 메커니즘을 갖추어야 합니다. 이를 위해 헬스 체크(health check)와 같은 모니터링 도구를 사용하여 실시간으로 상태를 감시하고, 문제가 발생하면 자동으로 복구하는 절차를 마련해야 합니다.
  2. 회로 차단기 패턴(Circuit Breaker Pattern)
    • 클라이언트 측에서는 고장난 마이크로서비스 인스턴스에 요청을 보내지 않도록 설계해야 합니다. 이를 위해 회로 차단기 패턴을 도입할 수 있습니다. 이 패턴은 마이크로서비스 간의 호출이 실패할 경우 일정 시간 동안 호출을 차단하여 시스템의 연쇄적인 오류를 방지합니다.
  3. 비동기 통신 및 메시지 큐
    • 동기식 호출 대신 비동기 통신을 사용하거나 메시지 큐를 도입하여 컴포넌트 간의 의존성을 줄이고, 한 컴포넌트의 오류가 다른 컴포넌트에 미치는 영향을 최소화할 수 있습니다. 이는 전체 시스템의 유연성과 안정성을 높입니다.
  4. 자동화된 배포 및 스케일링
    • 모든 과정은 자동화되어야 합니다. 이를 위해 쿠버네티스(Kubernetes)와 같은 오케스트레이션 도구를 사용하여 자동화된 배포, 스케일링, 장애 복구를 구현할 수 있습니다. 이러한 도구는 클러스터 내의 인스턴스 상태를 모니터링하고, 필요에 따라 자동으로 인스턴스를 추가하거나 제거합니다.

MSA 디자인 패턴

마이크로서비스 아키텍처(MSA)를 설계할 때 자주 사용되는 디자인 패턴들은 다양한 문제를 극복하기 위해 제공됩니다. 각 패턴의 문제점, 해결책, 해결책을 위한 필수 조건, 구현 방법을 다음과 같이 정리할 수 있습니다. 각 디자인 패턴의 구현은 추가로 포스팅할 예정이고 현 포스팅에서는 문제점과 해결방법에 대한 간단한 설명만을 소개하겠습니다. 

 

1. 서비스 탐색 (Service Discovery)

 

문제점:

  • 클라이언트가 마이크로서비스와 그 인스턴스를 찾을 수 있어야 합니다. 이유는 컨테이너에서 실행되는 마이크로서비스 인스턴스는 동적 IP를 할당받게 되는데 REST API를 클라이언트에서 호출하는 게 어렵습니다. 또한 서비스 인스턴스가 추가되거나 제거될 때마다 클라이언트가 이를 인지하고 업데이트할 수 있어합니다. 

해결책:

  • 현재 사용가능한 마이크로 서비스와 그 인스턴스를 추적하는 새 컴포넌트를 시스템 환경에 추가할 수 있습니다. 

필수 조건:

  • 마이크로서비스와 마이크로 서비스 인스턴스는 자동으로 등록 및 해지 할 수 있어야 합니다. 클라이언트는 마이크로서비스의 논리 엔드포인트에 요청을 하고, 요청은 마이크로서비스의 인스턴스 중 하나에서 처리될 수 있어야 합니다. 마이크로서비스에 대한 요청은 가용한 인스턴스임을 로드밸런서가 판단하여 할당할 수 있어야 합니다. 

구현 방법:

  • 바로 다음에 설명할 에지 서버 즉, 프록시 서버로써 동작할 컴포넌트를 추가합니다. 

2. 에지 서버 (Edge Server)

 

문제점:

  • 클라이언트가 여러 내부 서비스에 직접 접근하는 경우, 보안 및 관리가 어렵습니다. 여기서 말하는 보안이란 악의적인 클라이언트 요청으로부터 보호하는 것을 의미합니다. 때문에 서비스 간의 통신을 최적화하고 중앙에서 관리할 필요가 있습니다.

해결책:

  • 모든 요청이 필수로 거쳐야 하는 에지 서버를 통해 클라이언트의 요청을 수신하고, 내부 서비스로 요청을 라우팅합니다. 에지 서버에서 인증, 권한 부여, 로드 밸런싱, 캐싱 등을 처리합니다. 

필수 조건:

  • 외부 요청을 허용하는 마이크로 서비스만 요청을 라우팅 해야 합니다. 또한 OAuth, JWT, API 키 등으로 신뢰할 수 있는 클라이언트인지 확인할 수 있어야 합니다. 

3. 리액티브 마이크로서비스 (Reactive Microservices)

 

문제점:

  • 대개 Blocking I/O 모델을 사용하기 때문에 동기식 호출은 높은 지연 시간과 리소스 낭비를 초래할 수 있습니다. 때문에 대량의 요청을 효율적으로 처리하기 어렵습니다.

해결책:

  • Non Blocking I/O 모델을 사용함으로써 비동기식 통신과 리액티브 프로그래밍을 통해 서비스 간 통신을 최적화합니다. 또한 이벤트 기반 시스템을 구축하여 요청을 비동기적으로 처리합니다.

필수 조건:

  • 마이크로서비스는 의존하는 서비스가 중단되더라도 응답할 수 있도록 탄력적 설계가 필요합니다. 

구현 방법:

  • 메시징 시스템 (예: Kafka, RabbitMQ)

4. 구성 중앙화 (Centralized Configuration)

 

문제점:

  • 모든 마이크로서비스 인스턴스의 구성정보를 확인할 수 있어야 하는데 각 마이크로서비스의 구성을 개별적으로 관리하면 일관성을 유지하기 어렵습니다. 때문에 환경에 따라 구성을 쉽게 변경할 수 있는 장치가 필요합니다. 

해결책:

  • 구성 정보를 저장하는 새 컴포넌트를 추가하여 문제를 해결 할 수 있습니다. 

필수 조건:

  • 환경별 설정(dev, test, qa, prod)

구현 방법:

  • 구성 서버 (예: Spring Cloud Config, Consul)

5. 로그 분석 중앙화 (Centralized Logging)

 

문제점:

  • 분산된 마이크로서비스의 로그를 개별적으로 관리하면 문제를 추적하기 어렵습니다. 예를 들어 근본 원인이 되는 문제를 찾기 위해 각 컴포넌트의 인스턴스의 로그파일을 추적하는 것은 매우 어려운 작업일 수 있기 때문에 로그를 한 곳에서 모니터링하고 분석할 필요가 있습니다.

해결책:

  • 중앙 집중식 로그 수집 및 분석 시스템을 사용하여 모든 마이크로서비스의 로그를 중앙에서 관리합니다.

필수 조건:

  • 중앙 집중식 로그 수집 및 분석을 담당할 컴포넌트를 새로 추가합니다. 

구현 방법:

  • 로그 수집 도구 (예: Fluentd, Logstash)
  • 로그 저장소 및 분석 도구 (예: Elasticsearch, Splunk)

6. 분산 추적 (Distributed Tracing)

 

문제점:

  • 시스템 외부 호출을 처리하는 동안 공조 마이크로서비스 사이에서 흐르는 요청 및 메시지를 파악을 할 수 있어야 합니다. 

해결책:

  • 공조 마이크로서비스 사이의 처리 과정을 추적하려면 관련된 모든 요청 및 메시지에 상관 ID (correlation ID)를 넣어야 하고, 중앙화된 로깅 서비스에서 상관 ID 검색함으로써 로그 이벤트 찾을 수 있습니다. 

필수 조건:

  • 분산 추적 도구 (예: Zipkin, Jaeger)

7. 서킷 브레이커 (Circuit Breaker)

 

문제점:

  • 하나의 마이크로서비스가 실패하면 연쇄적으로 다른 서비스에 영향을 미칠 수 있습니다. 이를 연쇄 장애라고 합니다. 연쇄 장애는 실패한 서비스에 대한 요청이 반복되면 시스템 전체의 성능이 저하될 수 있습니다.

해결책:

  • 서킷 브레이커 패턴을 사용하여 호충의 대상 서비스에 문제가 있다는 것을 사전에 감지하고 새 요청을 보내지 않도록 차단 할 수 있습니다. 

필수 조건:

  • 서킷 브레이커가 대상 서비스에서 문제를 감지하면 시간 초과(time out)를 무시하고 바로 실패처리 합니다. 또한 서킷 브레이크는 반 열림 서킷 기능을 위해 프로브를 제공하는데, 이는 서비스가 정상 동작하는지 확인하고자 주기적으로 요청을 송신하고, 프로브는 서비스의 정상 동작을 감지하면 서킷을 닫음으로써 요청을 보내지 않도록 차단했던 조치를 해제할 수 있습니다. 

구현 방법:

  • 서킷 브레이커 라이브러리 (예: Hystrix, Resilience4j)

8. 제어 루프 (Control Loop)

 

문제점:

  • 여러 인스턴스가 여러 서버에 분산돼 있는 시스템 환경에선 중단되거나 지연된 마이크로서비스 인스턴스를 수동으로 감지하고 대체하는데 어려움이 있습니다. 

해결책:

  • 오 케스트레인션 도구를 사용합니다. 

필수 조건:

  • k8s

구현 방법:

  • 시스템의 상태를 실시간으로 모니터링합니다.
  • 사전 정의된 조건에 따라 자동으로 조정 작업을 수행합니다.
  • 피드백 루프를 통해 지속적으로 시스템 성능을 최적화합니다.

9. 모니터링 및 경고 중앙화 (Centralized Monitoring and Alerting)

 

문제점:

  • 하드웨어 사용량을 분석할 필요가 있습니다. 

해결책:

  • 중앙 집중식 모니터링 및 경고 시스템을 사용하여 모든 마이크로서비스의 상태를 중앙에서 모니터링하고, 문제 발생 시 경고를 발송합니다.

구현 방법:

  • 모니터링 및 경고 도구 (예: Prometheus, Grafana, Alertmanager) 추가 
반응형