반년만에 읽어보는 마이크로 서비스 개발
읽어야지, 읽어야지 하면서 순위만 밀려왔던 숙제 같은 책을 읽어본다.

도메인 주도 설계로 시작하는 마이크로 서비스 개발
w. 한정헌, 유해식, 최은정, 이주영
현대의 애플리케이션은 도처에 존재해서 사람들은 기기에 포함된 애플리케이션이 요청에 즉각 응답하고 항상 가동되길 기대한다.
2014년 요나스 보네르 등이 선언한 리액티브 선언문에는 4가지 특성을 강조한다.
< 리액티브 선언문의 4가지 특성 >
1. 응답성 (Responsive): 사용자에게 신뢰성 있는 응답을 빠르고 적절하게 제공하는 것
2. 탄력성 (Resilient): 장애가 발생하거나 부분적으로 고장 나더라도 시스템 전체가 고장나지 않고 빠르게 복구하는 능력
3. 유연성 (Elastic): 사용량에 변화가 있더라도 균일한 응답성을 제공하는 것
4. 메시지 기반 (Message Driven): 비동기 메시지 전달을 통해 위치 투명성, 느슨한 결합, 논블로킹 통신을 지향하는 것을 의미
내용 별 거 없음
예전에는 검증된 유명 제품을 사용해서 품질이 보장된다고 생각했는데,
요즘에는 클라우드 환경 내에서 다양한 기능을 지원하는 게 더 중요하다고 생각 중~
옛날에는 오랜 시간 동안 힘들게 구축했던 인프라를 AWS, 구글, 마이크로소프트, IBM 등이 IaaS, Paas 서비스를 통해 제공한다
마이크로서비스는 어떤 장비에도 구동될 수 있지만,
베어메탈 장비(어떤 소프트웨어도 담지 않은 하드웨어 서버 제품군 자체)로 구동한다면 각 마이크로서비스마다 장비를 구축해야 한다.
-> 그럼 당연히 가상 인프라 환경 검토하지
👶 VM과 컨테이너
가상 머신 제품과 컨테이너 제품 중 하나를 선택해야 한다.
도커의 특징
👶 컨테이너 오케스트레이션
컨테이너를 관리하기 위한 기술
컨테이너가 많아지면 컨테이너의 자동 배치 및 복제, 장애 복구, 확장 및 축소, 컨테이너 간 통신, 로드 밸런싱 등의 컨테이너 관리를 위한 기능이 필요해진다.
(ex. 도커 스윔, 아파치 메소스, 쿠버네티스)
👶 그 밖의 다양한 클라우드 인프라 서비스
AWS, Azure, GCP 등은 기존의 물리 서버, 네트워크, 스토리지, DB를 대체할 다양한 가상 서버, 가상 네트워크 및 가상 스토리지, 가상 DB 등을 제공하고 있다.
👧 개발 지원 환경: 데브옵스 인프라 구성
수동 빌드/배포 하면 시간이 진짜 많이 소요된다. 자동화가 절실하다...
자동화된 빌드나 배포 작업을 CI/CD라고 한다.
CI(Continuous Integration): 지속적인 통합
CD(Continuous Delivery, Continuous Deployment): 지속적 제공, 지속적인 배포
👧 빌드/배포 파이프라인 설계
빌드/배포 되는 과정 동안 수행해야 할 태스크가 정의된 것
자동화 될 요소들
매우 힘들어서 일부만 자동화 할 수 있다.
이건... 알아서 적용하기.
👧 마이크로서비스 생태계와 운영 관리 요소의 탄생
넷플릭스가 큰 거 했다.
아마존이 2006년에 IaaS 서비스인 EC2를 발표했고, 넷플릭스가 그즈음에 스트리밍 데이터베이스의 스토리지가 손실되는 대규모 서비스 장애를 겪는다.
그래서 모노리스 시스템에서 마이크로서비스 기반의 시스템으로 전환한다.
그랬더니 여러 문제점이 발생하고 이 해결법으로
넷플릭스OSS를 공개한다.
마이크로서비스 간의 라우팅과 로드 밸런싱을 위한 줄(Zuul)과 리본(Ribbon), 모니터링을 위한 히스트릭스(Hystrix), 서비스 등록을 위한 유레카(Eureka)...
이후 2013년에 도커가 등장하고,
이쯤에 스프링에서 스프링 부트를 발표하고,
최근에는 구글의 쿠버네티스가 등장했다.
👧 경험으로 획득한 지혜: 마이크로 서비스 관리/운영 패턴
넷플릭스의 오픈소스로 인해 마이크로서비스 관리와 운영을 지원하는 전형적인 패턴이 생겼다.
(ex. API 게이트웨이, 서비스 디스커버리, 모니터링, 트레이싱)
👧 스프링 클라우드: 스프링 부트 + 넷플릭스 OSS
1. 모든 마이크로서비스(스프링 클라우드 서비스를 포함한)는 인프라에 종속되지 않도록 데이터베이스, 파일 등에 저장된 환경 설정 정보를 형상관리 시스템에 연계된 'Config 서비스'에서 가져와 설정 정보를 주입한 후 클라우드 인프라의 개별 인스턴스로 로딩된다.
2. 로딩과 동시에 '서비스 레지스트리'에 자신의 서비스명과 클라우드 인프라부터 할당받은 물리 주소를 매핑해서 등록한다.
3. 클라이언트가 'API 게이트웨이'를 통해 마이크로서비스에 접근하고, 이때 API 게이트웨이는 적절한 라우팅 및 부하 관리를 위한 로드 밸런싱을 수행한다.
4. 또한 API 게이트웨이에서 클라이언트가 각 서비스에 접근하기 위한 주소를 알기 위해 '서비스 레지스트리' 검색을 통해 서비스의 위치를 가져온다.
5. 동시에 API 게이트웨이는 클라이언트가 각 서비스에 접근할 수 있는 권한이 있는지 '권한 서비스'와 연계해 인증/인가 처리를 수행한다.
6. 이러한 모든 마이크로서비스 간의 호출 흐름은 '모니터링 서비스'와 '추적 서비스'에 의해 모니터링되고 추적된다.
👧 다양한 서비스의 등록 및 탐색을 위한 서비스 레지스트리, 서비스 디스커버리 패턴
여러 개의 백엔드 마이크로서비스를 어떻게 호출할지, 인스턴스가 여러 개로 복제됐다면 어떻게 부하를 적절히 분산할까?
-> 서비스 디스커버리 패턴을 보세요!
너무 길어서 이건 책 보거라...
👧 외부 구성 저장소 패턴
데이터베이스 연결 정보, 파일 스토리지 정보가 애플리케이션에 포함되면 변경 시 반드시 재배포 해야 한다. 그럼 서비스 중단을... 해야 하는데!
그래서 외부 구성 저장소 패턴이 나왔댄다.
클라우드에서 운영되는 애플리케이션은 특정한 배포 환경에 종속된 정보를 두면 안 된다는 원칙이다.
분리해야 할 환경 정보?
데이터베이스 연결 정보, 배포 시 변경해야 할 호스트명, 백엔드 서비스의 연결을 위한 리소스 정보, 서비스가 기동되는 개발 서버, 테스트, 운영 서버의 IP 주소와 포트 정보 등
스프링 클라우드 컨피그를 이용하여 애플리케이션에서 분리하고 컨피그 서비스를 통해 런타임 시 주입되게 한다.
👧 인증/인가 패턴
원래는 세션에 사용자의 로그인 정보와 권한 정보를 관리했는데 마이크로서비스를 이용하면 세션 데이터가 손실될 수 있다.
그래서 클라이언트 토큰으로
1. 사용자가 서버에 로그인 하면
2. 서버에서 인증 확인 후 토큰을 생성하고
3. 서버에서 사용자 정보를 담은 토큰 전송하고
4. 사용자가 토큰으로 리소스 접근 요청하고
5. 서버가 토큰을 검토해서 인가 허용하고
6. 서버가 리소스 접근을 허용한다.
이 방법은 API 게이트웨이를 사용해서 인증 서비스로 연결되도록 할 수 있다.
👧 장애 및 실패 처리를 위한 서킷 브레이커 패턴
A라는 서비스가 B라는 서비스를 호출해서 자신의 서비스를 제공하는데,
B에서 장애가 발생하면 동기 요청의 성격상 A가 계속 기다려서 사용자는 A에서 장애가 발생한 것처럼 생각한다.
서킷 브레이커 패턴은
B 서비스 호출에 대한 연속 실패 횟수가 임곗값을 초과하면 회로 차단기가 작동해서 이후에 서비스를 호출하려는 모든 시도를 즉시 실패하게 만든다.
그리고 폴백(fallback) 메서드를 지정해 두면 장애가 발생했을 때 장애가 발생했을 대 폴백 메서드가 자연스럽게 처리를 진행한다.
👧 모니터링과 추적 패턴
그럼 장애를 어떻게 감지하나요...?
스프링 클라우드에서는 히스트릭스라는 라이브러리를 제공한다.
히스트릭스 대시보드로 요청을 실시간으로 볼 수 있다.
분산은 책으로 보삼!
👧 중앙화된 로그 집계 패턴
서비스에서 발생한 이벤트 스트림 형태의 로그를 수집하고 살펴볼 도구가 필요하다.
대표적으로 사용하는 것이 ELK 스택이다.
예를 들자면,
각 서비스에 로그스태시가 설치되어 각 로그를 수집해서 레디스 저장소에 보낸다.
또 다른 서비스에서는 엘라스틱서치와 키바나로 로그 중앙 관리 저장소와 대시보드 서비스를 각각 구축한다.
로그가 중앙 레디스에 쌓이면 레디스에서 중앙 관리 저장소에 로그를 보내고,
로그 저장소에 엘라스틱서치 엔진이 로그를 인덱승해서 키바나로 보여준다.
(레디스 왜 두나요? 로그 스트림이 몰리면 성능 문제 생겨용)
서비스 메시 패턴과 이스티오는 처음 들어서 조금 더 공부해야 할 듯... 하다.
👧 프론트엔드
업무 기능 하나가 변경되어 재배포해야 할 때,
백엔드: 수정 후 하나의 서비스로 독립적 배포
프론트엔드: 덩어리이기 때문에 변경되지 않은 다른 기능도 함께 빌드 및 배포 됨
해결 방안이 밑에...
👧 UI 컴포지트 패턴 또는 마이크로 프론트엔드
기능별로 분리하고 이를 조합하기 위한 프레임(frame) 형태의 부모 창을 통해 각 프론트엔드를 조합해서 동작하게 한다.
👧 마이크로서비스 통신 패턴
메시지 브로커
메시지를 보내는 생산자(producer)와 메시지를 가져다가 처리하는 소비자(consumer)가 서로 직접 접속하지 않고 메시지 브로커에 연결된다.
저장소 분리 패턴은,
각 마이크로서비스는 각자의 비즈니스 처리를 위한 데이터를 직접 소유한다.
데이터를 다른 서비스에 직접 노출하지 않음
API를 통해서만 접근
저장소를 자율적으로 선택할 수 있음
변경의 파급 효과가 적음
👧 분산 트랜잭션 처리 패턴
비즈니스 정합성과 데이터 일관성을 어떻게 보장할 것인가?
사가(Saga) 패턴
- 분산된 서비스를 하나의 트랜잭션으로 묶지 않고 각 로컬 트랜잭션과 보상 트랜잭션을 이용
- 로컬 트랜잭션이 자신의 데이터베이스를 업데이트 하고, 다음 로컬 트랜잭션을 트리거하는 메시지 또는 이벤트를 게시하여 데이터의 일관성을 맞춤
- 트랜잭션 실패 시 보상 트랜잭션을 통해 이전 로컬 트랜잭션이 작성한 변경 사항을 취소
👧 데이터 일관성에 대한 생각의 전환: 결과적 일관성
예전에는 실시간으로 일관성을 지켜야 하는 경우가 많았는데,
모든 비즈니스 처리가 반드시 실시간성을 요구하는 것이 아니다
결과적 일관성: 데이터의 일관성이 실시간으로 맞지 않더라도 어느 일정 시점이 됐을 때 일관성을 만족해도 되는 것
👧 읽기와 쓰기 분리: CQRS 패턴
CQRS: Command Query Responsibility Segregation, 명령 조회 책임 분리
도입 원인: 서비스 인스턴스를 스케일 아웃 해서 여러 개로 실행한 경우 데이터 읽기/수정 작업으로 인한 리소스 교착 상태가 발생할 수 있다
방식: 저장소에 읽기 모델과 쓰기 모델을 분리하는 방식으로 변화시킨다
일관성이 깨지게 되니까 이벤트 주도 아키텍처 도입
명령 서비스는 저장소에 데이터를 쓰면 이벤트를 발생시켜 메시지 브로커에 전달
조회 서비스는 메시지 브로커의 이벤트를 구독하고 있다가 이벤트 데이터를 가져와 최신 상태로 동기화
관련한 API 통신에 도입되는 CQRS는 실제로 도움이 될 것으로 보인다.
책을 참고하자 ^^