MSA 개요
개요
2011년 기준 아마존의 비즈니스 서비스가 배포되는 주기는 11.6초 그리고 2019년에는 초당 1.5번.
이것이 가능한 이유는 여러가지가 있을 것이다. 그중 인프라 스트럭쳐를 살펴보자.
서버를 도입하고 네트워크를 구축한 뒤 각 서버마다 운영체제를 설치하고 서비스에 필요한 소프트웨어를 설치하는 과정으로 진행되고 여기에 소요되는 시간은 며칠에서 몇 달까지 걸린다고한다.
새로운 서비스 개발을 위한 프로젝트를 시작한다고 했을 때 인프라를 구축하기 위한 초기 투자 비용을 고려해야하고 이것들을 지속적으로 관리하는 비용을 고려해야한다. 그러나 최근에는 클라우드 인프라 서비스를 통해 해결되었다.
이러한 클라우드 인프라는 사용량에 따라 비용이 청구된다. 서비스의 상황에 따라 유연하게 변경될 수 있다는 것이다.
이를 이용해 간단하게 인프라를 구축할 수 있다.
스케일업 스케일 아웃
사용량 증가에 따른 성능 및 가용성을 높이는 방법으로는 크게 두 가지가 있다. 스케일 업, 스케일 아웃.
스케일 업은 기존 시스템의 물리적 용량을 증가시켜 성능을 높이는 방법이다.
스케일 아웃은 기존 시스템과 용량이 같은 다수의 장비를 병행 추가하여 가용성을 높이는 방법이다.
이중 좀 더 유연한 방법은 스케일 아웃이다. 이벤트 기간동안에 트래픽이 증가하는 서비스가 있을 것이고 그렇지 않은 서비스가 있을 것이다. 이럴 때 특정 기간에만 서버를 증설하여 문제를 해결하는 스케일 아웃을 사용한다.
이를 적절히 활용할 수 있는 방법은 하나의 덩어리로 묶여있는 애플리케이션을 여러개의 애플리케이션으로 분리하는 것이다.
모노리스와 마이크로서비스
모노리스는 전통적인 시스템의 구조로 하나로 묶여 개발되는 애플리케이션이다. 보통 이를 사용자 인터페이스와 데이터베이스, 서버쪽 애플리케이션. 이렇게 3개의 부분으로 구성된다.
서버쪽 애플리케이션은 논리적인 단일체로 아무리 작은 변화라도 전체를 빌드해서 배포해야한다. 그리고 일체식 애플리케이션은 단일 프로세스에서 실행되기 때문에 확장이 필요할 경우 특정 기능만 확장할 수 없고 전체 애플리케이션을 동시에 확장해야한다. 보통 밸런서를 앞에 두고 여러 인스턴스 위에 큰 덩어리를 복제해 수평으로 확장하는 방식을 사용한다.
작은 변화가 일어났을 때는 이 모든 시스템을 다시 빌드하고 배포해야한다는 문제가 있다.
마이크로서비스는 서버측이 여러 개의 조각으로 구성되어있어 각 서비스가 별개의 인스턴스로 로딩된다. 만약 특정 서비스를 확장하고 싶다면 그 서비스만 스케일 아웃하면 된다.
인프라 구성요소
인프라란 엔터프라이즈 IT환경을 운영하고 관리하는데 필요한 근간이되는 하드웨어, 소프트웨어, 네트워킹, 운영체제, 데이터베이스 등을 포괄하는 단어이다.
예전에는 오랜 시간과 비용을 동원해 구축했던 인프라이지만 이제는 여러 IaaS, PaaS서비스를 통해 쉽고 편하게 이용할 수 있다.
현재 상황에서는 기존의 물리적 장비를 구매해서 구축하느냐 혹은 가상화 기술을 이용해 구축하느냐이다.
가상 환경 기술로는 도커가 가장 유명하다.
이렇게 컨테이너 기술을 선택했다면 또 다른 고민거리가 생긴다.
컨테이너가 많아지면 컨테이너의 배치, 복제, 장애복구 등 컨테이너 관리를 위한 기능이 필요해진다.
이러한 기술을 컨테이너 오케스트레이션이라고 한다.
개발 공부를 하다보면 쿠버네티스라는 이름을 한 번쯤은 들어봤을 것이다. 쿠버네티스가 바로 컨테이너 오케스트레이션 툴이다.
스프링 클라우드
스프링 클라우드는 스프링 프레임워크를 개발하고 있는 피보탈에서 넷플릭스가 공개한 줄, 유레카등의 넷플릭스 오픈 소스를 스프링 부트 프레임워크 기반으로 쉽게 사용할 수 있도록 통합한 것이다.
레지스트리
마이크로서비스로 도메인별로 서버를 나누었다면 프론트엔드 클라이언트가 여러 개의 백엔드 서비스를 어떻게 호출해야할까?
이를 위한 패턴이 바로 디스커버리 패턴
이다. 클라이언트가 여러 개의 마이크로서비스를 호출하기 위해서는 최적 경로를 찾아주는 라우팅 기능과 로드밸런싱 기능이 필요하게 되는데 라우팅 기능을 줄이 담당하고 로드밸런싱은 리본이 담당한다.
라우팅이 제 역할을 하기 위해서는 서비스 명칭에 해당하는 IP주소를 알아야한다. 그러나 클라우드 환경에서 동적으로 변경되는 백엔드의 유동 IP정보를 매번 전송받아 변경하기보다 특정 공간에서 이러한 정보를 관리하는 것이 좋다.
이를 넷플릭스 OSS의 유레카가 담당하고 이러한 패턴을 서비스 레지스트리 패턴이라고 한다.
서비스 인스턴스가 로딩될 때 자신의 서비스 이름과 할당된 IP주소를 레지스트리 서비스에 등록한다. 그 다음 클라이언트가 해당 서비스를 호출할 때 라우터가 레지스트리 서비스를 검색해 해당 서비스의 이름과 등록된 IP주소를 확인한 후 호출한다.
저장소
모노리스 시스템의 저장소는 통합 저장소 방식으로 모듈은 분리하지만 데이터베이스는 분리하지 않고 다른 모듈에서의 호출을 허용하는 구조이다. 이런 상황에서는 특정 데이터베이스에 종속되고 성능 문제가 발생했을 때 SQL튜닝이나 저장소의 스케일 업에 의존할 수 밖에 없다.
하나의 시스템을 여러 마이크로서비스로 분리하더라도 요청이 증가할 경우 여러 서비스에서 호출되는 통합 데이터베이스만 여전히 바쁜 상황이 되어 마이크로서비스의 장점이자 존재 의의인 자동 스케일 아웃이 별 소용이 없어질 수 있다.
따라서 마이크로서비스에선 저장소를 분리하여 각 도메인에서 사용하는 저장소만 갖도록 설계할 수 있다.
만약 하나의 도메인에서 요청을 처리하면서 다른 도메인에도 기록이 되어야한다면 어떻게 할까?
다른 서비스의 API를 통해서 호출하는 것이다. 그러나 이렇게 여러 개의 서버에 걸쳐있는 비즈니스 처리를 수행해야하는 경우 정합성과 데이터 일관성을 어떻게 보장할 수 있을까 고민해봐야한다.
그 방법중 하나는 이렇게 분산된 서비스를 하나의 트랜잭션으로 묶는 것이다.
분산 트랜잭션 처리 시 2단계 커밋 방법이 있다. 이는 분산 트랜잭션에 포함되어있는 모든 노드가 커밋되거나 롤백되도록 하는 매커니즘이다.
이를 위한 패턴이 바로 Saga패턴이다.
Saga패턴은 각 서비스의 로컬 트랜잭션을 순차적으로 처리하는 패턴이다.
여러개의 서비스를 하나의 트랜잭션으로 묶지 않고 각 로컬 트랜잭션과 보상 트랜잭션을 이용해서 데이터의 정합성을 맞춘다.
자신의 데이터베이스를 업데이트한 뒤, Saga내부에 있는 다음 로컬 트랜잭션을 트리거하는 메시지 또는 이벤트를 게시하여 일관성을 맞춘다.
만약 서비스에서 트랜잭션의 처리에 실패했을 경우 그 서비스의 앞선 다른 서비스에서 처리한 트랜잭션을 되돌리게 하는 트랜잭션이다.
주문 서비스를 예로 들어보면, 주문 처리가 들어오면 가주문을 생성하고 주문자의 정보가 담긴 주문 생성이벤트를 발행하고 트랜잭션을 종료한다.
고객 서비스에선 이벤트를 확인하고 다음 처리를 수행한다.
주문자의 정보로 신용한도를 조회하고 신용한도가 충족되면 신용 승인 이벤트를, 만약 충족되지 못하면 신용한도 초과 이벤트를 발행한다.
그 다음 주문 서비스는 고객 서비스가 발행한 이벤트를 확인하고 다음 처리를 수행한다. 만일 신용 승인이벤트였다면 주문을 하고 신용한도 초과이벤트라면 주문처리를 취소한다.
CQRS
Command Query Responsibility Segregation
직역하자면 명령 조회 책임 분리다.
사용자의 비즈니스 요청은 시스템 상태를 변경하는 명령과 시스템의 상태를 조회하는 부분으로 나눌 수 있다.
일반적으론 상태를 조회하는 요청이 많이 쓰인다. 즉, CRUD중에서 Read가 다른 작업들보다 훨씬 많이 쓰인다.
서비스내에 이러한 기능을 넣어두면 조회 요청 빈도가 증가함에 따라 다른 명령 기능도 함께 확장해야 하므로 효율적이지 않다.
따라서 쓰기 전략과 조회 전략을 각각 분리하여 쓰기 시스템의 부하를 줄이고 조회 대기 시간을 줄이는 이점을 얻을 수 있다.
DB도 CUD 작업이 구현되어있는 서비스는 RDB로 구성하고 R 작업이 구현되어있는 서비스는 NoSQL로 구성하여 NoSQL이 조회속도가 빠르다는 점을 활용할 수 있다.