이것은 마이크로서비스로 어플리케이션을 작성하는 시리즈 중 다섯번째 Article이다. 첫번째 Article에서는 마이크로서비스 아키텍처 패턴을 소개하고, 마이크로서비스를 사용하는 것에 대한 장단점에 대해서 논의했다. 시리즈의 두번째와 세번째 Article에서는 마이크로서비스 내에서 다양한 측면의 통신에 대해서 설명했다. 네번째 Article에서는 서비스 검색과 밀접하게 관련된 문제에 대해서 살펴보았다. 이번 Article에서는 기어를 변속하여 마이크로서비스 아키텍처에서 떠오르는 Distributed Data Management(분산 데이터 관리)에 대해서 살펴보자.

Microservices and the Problem of Distributed Data Management
(마이크로서비스와 분산 데이터 관리 문제)

Monolithic 어플리케이션은 일반적으로 단일 관계형 데이터베이스(Single Relational Database)를 가지고 있다. 관계형 데이터베이스를 사용할 때의 주요 이점은 어플리케이션이 몇가지 중요한 점들을 보장하는 ACID 트랜잭션을 사용할 수 있다는 것이다.

  - Atomicity(원자성) : 변경 사항들이 원자적으로 적용된다. (트랜잭션과 관련된 작업들이 부분적으로만 실행되고 중단되지 않는 것을 보장하는 능력. 즉, 중간 단계까지만 실행되고 나머지는 실패하는 일이 없도록 하는 것.)
  - Consistency(일관성) : 데이터베이스의 상태는 항상 일관성이 있다. (트랜잭션을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스를 유지하는 능력.)
  - Isolation(고립성) : 트랜잭션이 동시에 실행되는 경우라도, 순차적으로 실행된다. (트랜잭션 수행 시, 동시에 실행되더라도 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 능력.)
  - Durability(지속성) : 한번 커밋된 트랜잭션은 실행취소 되지 않는다. (성공적으로 수행된 트랜잭션은 영원히 반영되어야 함.)

결과적으로 어플리케이션은 트랜잭션을 간단히 시작하고, 여러 행을 변경(삽입, 업데이트, 삭제)하고, 트랜잭션을 커밋할 수 있다.

관계형 데이터베이스를 사용하는 또 다른 큰 이점은 풍부하고, 선언적이며, 표준화된 Query Language인 SQL을 제공한다는 것이다. 여러 테이블에서 데이터를 결합하는 쿼리를 쉽게 작성할 수 있다. RDBMS Query Planner는 쿼리를 실행하기 위해 가장 적합한 방법을 결정한다. 어떻게 데이터베이스에 접근하는 지와 같은 저수준(low-level)의 세부 사항에 대해서 걱정할 필요 없다. 그리고 어플리케이션의 모든 데이터(어플리케이션이 필요로 하는 모든 데이터)가 하나의 데이터베이스에 있기 때문에 질의하기도 쉽다.

안타깝게도, 마이크로서비스 아키텍처로 전환하게 된다면 Data Access가 훨씬 더 복잡해진다. 그것은 각 마이크로서비스가 소유한 데이터가 개별 마이크로서비스 전용이고, API를 통해서만 접근할 수 있기 때문이다. 데이터를 캡슐화하는 것은 마이크로서비스가 서로 느슨하게 결합되고, 서로 독립적으로 진화하는 것을 보장한다. 만약 여러 서비스가 동일한 데이터에 접근한다면, 스키마 업데이트에는 시간이 오래 걸리고, 모든 서비스에 업데이트하기까지 조정이 필요하다.

설상가상으로, 다른 마이크로서비스는 종종 다른 종류의 데이터베이스를 사용한다. 최신 어플리케이션은 다양한 종류의 데이터를 저장 및 처리하고, 그에 따라 관계형 데이터베이스가 언제나 최고의 선택은 아니다. 어떤 Use Case에서는 특정 NoSQL 데이터베이스가 더 편리한 데이터 모델과 더 좋은 성능, 확장성을 가지고 있을 수 있다. 예를 들면, 텍스트를 저장하고, 질의하는 서비스는 ElasticSearch와 같은 텍스트 검색 엔진을 사용하는 것이 의미가 있다. 이와 유사하게, Social Graph 데이터를 저장하는 서비스는 Neo4j와 같은 Graph Database를 사용해야 한다. 결과적으로, 마이크로서비스 기반 어플리케이션은 종종 SQL과 NoSQL이 혼합된 형태, 소위 Polyglot Persistence(데이터를 저장할 때, 여러 가지 데이터 저장 기술을 사용하여 최고의 해결책을 찾는 것) 접근법을 사용한다.

데이터 스토리지에 대한 파티션된 Polyglot Persistence Architecture는 느슨하게 결합된 서비스와 더 좋은 성능, 확장성을 포함하여 많은 이점이 있다. 그러나, 일부 분산 데이터 관리 문제가 발생한다.

첫번째 문제는 여러 서비스간의 데이터 일관성을 유지하기 위해 비즈니스 트랜잭션을 어떻게 구현하는가이다. 왜 이것이 문제인지 알아보려면, 온라인 B2B Store 예제를 살펴보자. Customer Service는 신용 한도를 포함하여 고객 정보를 유지 관리한다. Order Service는 주문을 관리하고, 신규 주문이 고객의 신용 한도를 초과하지는 않는지를 검증해야 한다. Monolithic Application에서는 Order Service가 ACID 트랜잭션을 사용하여 간단히 사용 가능한 신용한도를 확인하고 주문을 생성할 수 있다.

반대로, 다음 다이어그램에서 보는 것처럼, 마이크로서비스 아키텍처에서는 ORDER와 CUSTOMER 테이블이 각각의 서비스 전용이다.

사용자 삽입 이미지


Order Service는 CUSTOMER 테이블에 직접적으로 접근이 불가능하다. Customer Service가 제공하는 API만 사용할 수 있다. Order Service는 잠재적으로 two-phase commit(2PC)라고 알려진 분산 트랜잭션을 사용해야만 한다. 그러나 2PC는 일반적으로 최근 어플리케이션에서 실행가능한 옵션이 아니다. CAP Theorem에서는 가용성과 ACID-Style의 일관성 중에서 선택할 필요가 있다. 그리고 가용성이 일반적으로 더 나은 선택이다. 더우기, 대부분의 NoSQL 데이터베이스와 같은 많은 현대 기술은 2PC를 지원하지 않는다. 서비스와 데이터베이스 간에 데이터 일관성을 유지하는 것이 필수적이기 때문에, 다른 솔루션이 필요하다.

두번째 문제는 여러 서비스에서 데이터를 조회하는 쿼리를 어떻게 구현하느냐이다. 예를 들면, 어플리케이션에서 고객과 고객의 최근 주문 내역을 표시하는 경우를 생각해 보자. Order Service는 고객의 주문 내역을 조회하는 API를 제공하고, 어플리케이션 측면의 Join을 사용하여 데이터를 조회할 수 있다. 어플리케이션은 Customer Service에서 고객을 조회하고, Order Service에서 고객의 주문 내역을 조회할 수 있다. 그러나, Order Service가 단지 Primary Key로 주문 내역을 조회할 수 있다고 가정해 보자.(아마도 단지 Primary Key기반 조회만 지원하는 NoSQL데이터베이스를 사용한다고 가정해 보자.) 이와 같은 상황에서는, 필요한 데이터를 조회하는 확실한 방법이 없다.

받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/772

Summary
(요약)

마이크로서비스 어플리케이션에서 실행중인 서비스 인스턴스의 집합은 동적으로 변경된다. 인스턴스는 자동으로 네트워크 상의 위치를 할당받는다. 결과적으로 클라이언트에서 서비스로 요청을 보내기 위해서는, 서비스 검색 메커니즘을 사용해야만 한다.

서비스 검색의 핵심 부분은 서비스 레지스트리이다. 서비스 레지스트리는 이용 가능한 서비스 인스턴스에 대한 데이터베이스이다. 서비스 레지스트리는 Management API와 Query API를 제공한다. 서비스 인스턴스는 Management API를 통해서 서비스 레지스트리에 등록되거나, 등록이 취소된다. Query API는 이용 가능한 서비스 인스턴스를 검색하고자 하는 시스템 구성 요소에 의해 사용된다.

클라이언트 측면의 검색과 서버 측면의 검색, 2가지 주요 서비스 검색 패턴이 있다. 클라이언트 측면의 서비스 검색을 사용하는 시스템에서는 클라이언트가 서비스 레지스트리에 질의하고, 이용 가능한 인스턴스를 선택하고, 요청을 한다. 서버 측면의 검색을 사용하는 시스템에서는 클라이언트는 router를 통해서 요청을 하고, router가 서비스 레지스트리에 질의하고, 이용 가능한 인스턴스에 요청을 전달한다.

서비스 인스턴스를 서비스 레지스트리에 등록 및 등록 취소하는 2가지 주요 방법이 있다. 한가지 옵션은 서비스 인스턴스가 자체 등록 패턴(self-registration pattern)을 사용하여 서비스 레지스트리에 자신을 등록하는 것이다. 다른 옵션은 다른 시스템 컴포넌트가 서비스 대신 3rd-party 등록 패턴(third-party registration pattern)을 사용하여 다른 서비스를 통해 등록 및 등록 취소를 하는 것이다.

일부 배포 환경에서는, Netflix Eureka나 etcd, Apache Zookeeper와 같은 서비스 레지스트리를 사용하여 자체 서비스 검색 인프라를 구축할 필요가 있다. 다른 배포 환경에서는, 서비스 검색은 내장되어 있다. 예를 들면, Kubernetes와 Marathon에서는 서비스 인스턴스의 등록 및 등록 취소를 처리한다. 또한, 서버 측면의 검색 router 역할을 하는 각 Cluster 호스트에서 Proxy를 실행한다.

NGINX와 같은 HTTP Reverse Proxy와 Load Balancer는 서버 측면의 검색 Load Balancer로 사용될 수도 있다. 서비스 레지스트리는 NGINX에 라우팅 정보를 Push하고, 적절한 구성을 업데이트하도록 호출할 수 있다. 예를 들어, Consul Template를 사용할 수 있다. NGINX Plus는 추가적인 동적 재설정 메커니즘을 지원한다. - DNS를 사용하여 레지스트리에서 서비스 인스턴스에 대한 정보를 가져올 수 있고, 원격 재설정을 위한 API를 제공한다.

앞으로의 블로그 포스트에서는 마이크로서비스의 다른 측면에 대해서 계속해서 깊게 다룰 것이다. 이 시리즈의 향후 Article 릴리즈에 대한 소식을 받기 원한다면, (아래 양식의) NGINX 메일링 리스트에 등록하라.
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/771

Service Registration Options
(서비스 등록 옵션들)

앞서 언급했듯이, 서비스 인스턴스는 서비스 레지스트리를 통해서 등록되고, 등록 취소 되어야 한다. 등록 및 취소를 다루는 몇가지 다른 방법이 있다. 한가지 옵션은 서비스 인스턴스가 자체 등록 패턴으로 등록하는 것이다. 다른 옵션은 서비스 인스턴스의 등록을 3rd-party 등록 패턴으로, 다른 서비스 컴포넌트에서 다루는 것이다. 먼저 자체 등록 패턴에 대해서 살펴보자.

The Self-Registration Pattern
(자체 등록 패턴)

자체 등록 패턴을 사용하는 경우, 서비스 인스턴스는 직접 서비스 레지스트리에 등록하고 등록을 취소한다. 또한 만약 필요하다면, 서비스 인스턴스는 등록이 만료되지 않도록 heartbeat 요청을 보낸다. 다음 다이어그램은 이 패턴의 구조를 보여준다.

사용자 삽입 이미지

이러한 접근법의 좋은 예제는 Netflix OSS Eureka 클라이언트이다. Eureka 클라이언트는 서비스 인스턴스의 등록 및 등록 취소에 대한 모든 측면을 다룬다. 서비스 검색을 포함하여 다양한 패턴을 구현한 Spring Cloud 프로젝트에서는 Eureka로 서비스 인스턴스를 쉽게 자동으로 등록하게 한다. Java Configuration Class에서 @EnableEurekaClient Annotation만으로 간단히 사용할 수 있다.

자체 등록 패턴은 다양한 이점과 단점이 있다. 한가지 이점은 상대적으로 간단하고 다른 시스템 요소가 필요없다는 것이다. 그러나, 큰 단점은 서비스 레지스트리와 서비스 인스턴스가 묶여 있다는 것이다. (상호 의존 관계임) 서비스에서 사용하고 있는 프로그래밍 언어와 프레임워크마다 등록 코드를 구현해 주어야 한다.

서비스에서 서비스 레지스트리를 분리하도록 대체하는 접근법은 3rd-party 등록 패턴이다.

The Third-Party Registration Pattern
(3rd-Party 등록 패턴 : 제 3자 등록 패턴)

3rd-Party 등록 패턴을 사용할 때, 서비스 인스턴스는 서비스 레지스트리에 스스로를 등록할 책임이 없다. 대신에 서비스 레지스트라로 알려진 또다른 서비스 컴포넌트가 등록을 처리한다. 서비스 레지스트라는 배포환경을 polling하거나 이벤트에 등록하여 실행중인 인스턴스 집합에 대한 변경을 추적한다. 서비스 레지스트라는 새로 이용 가능한 서비스 인스턴스를 알게 되면, 인스턴스를 서비스 레지스트리에 등록한다. 또한 서비스 레지스트라는 서비스 인스턴스가 종료되었을 때, 등록을 취소한다. 다음 다이어그램은 이 패턴의 구조를 보여준다.

사용자 삽입 이미지

서비스 레지스트라의 한가지 예제는 Open Source Registrator 프로젝트가 있다. Open Source Registrator 프로젝트는 Docker 컨테이너로 배포된 서비스 인스턴스들을 자동으로 등록하고, 등록을 취소한다. Registrator는 etcd와 Consul을 포함하여 다양한 서비스 레지스트리를 지원한다.

서비스 레지스트라의 또다른 예제는 NetflixOSS Prana를 들 수 있다. 주로 Non-JVM 언어로 작성된 서비스를 대상으로 하는, 서비스 인스턴스와 함께 실행되는 사이드카 어플리케이션이다. (Sidecar Application : 주로 사용하는 메인 기능 이외 부가적으로 사용하여 메인 기능을 향상시키는 어플리케이션) Prana는 Netflix Eureka를 사용하여 서비스 인스턴스를 등록하고, 등록을 취소한다.

서비스 레지스트라는 배포 환경에 내장된 구성 요소이다. Autoscaling Group에 의해 생성된 EC2 인스턴스는 ELB(Elastic Load Balancer)에 자동으로 등록될 수 있다. Kubernetes 서비스는 자동으로 등록되어 검색에 사용할 수 있다.

3rd-Party 등록 패턴은 다양한 이점과 단점을 가지고 있다. 주요 이점은 서비스가 서비스 레지스트리와 분리되어 있다는 것이다. 개발자들이 사용한 프로그래밍 언어와 프레임워크 마다 서비스 등록 로직을 구현할 필요가 없다. 대신에, 서비스 인스턴스 등록은 전용 서비스에서 중앙집중식으로 이루어진다.

이 패턴의 한가지 단점은 배포 환경에 내장되어 있지 않으면, 설치 및 관리할 필요가 있는 또다른 고가용 시스템 컴포넌트라는 것이다.


받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/770

The Service Registry
(서비스 레지스트리)

서비스 레지스트리는 서비스 검색의 핵심적인 부분이다. 서비스 레지스트리는 서비스 인스턴스의 네트워크 상 위치를 포함하고 있는 데이터베이스이다. 서비스 레지스트리는 고가용성이어야 하고, 최신 상태가 되어 있어야 한다. 클라이언트는 서비스 레지스트리를 통해서 얻은 네트워크 위치를 캐시할 수 있다. 그러나 캐시한 정보는 점차적으로 오래된 데이터가 되고, 클라이언트는 서비스 인스턴스를 검색할 수 없게 된다. 결과적으로, 서비스 레지스트리는 일관성을 유지하기 위해서 복제 프로토콜을 사용하는 서버 클러스터로 이루어진다.

앞에서 언급했듯이, Netflix Eureka는 서비스 레지스트리의 좋은 예이다. 서비스 인스턴스를 등록하고 질의하기 위한 REST API를 제공한다. 서비스 인스턴스는 POST 요청을 사용하여 네트워크 상의 위치를 등록한다. 매 30초마다 PUT 요청을 사용하여 등록 정보를 새로 고쳐야 한다. 등록 정보는 HTTP DELETE 요청을 사용하여 삭제되거나, 등록 시간 초과에 의해 제거된다. 예상한 것처럼, 클라이언트는 HTTP GET 요청을 통해서 등록된 서비스 인스턴스를 조회할 수 있다.

Netflix는 각 Amazon EC2 가용성 존에서 하나 이상의 Eureka서버를 실행하여 고가용성을 실현한다. 각 Eureka 서버는 탄력적인 IP 주소를 갖는 EC2 인스턴스에서 실행된다. DNS TEXT 레코드는 Eureka Cluster 설정을 저장하는데 사용되고, Eureka Cluster 설정은 가용성 존에서부터 Eureka 서버의 네트워크 상 위치 목록까지의 맵이다. Eureka 서버가 시작되면, Eureka Cluster 설정을 얻기 위해서 DNS에 질의하고, 피어의 위치를 찾은 후 사용되지 않은 Elastic IP 주소를 할당한다.

Eureka 클라이언트 - 서비스와 서비스 클라이언트 - 는 Eureka 서버의 네트워크상 위치를 찾기 위해서 DNS에 질의한다. 클라이언트는 동일한 가용성 존에 있는 Eureka 서버를 사용하는 것을 선호한다. 그러나 아무것도 사용할 수 없는 경우에는 또다른 가용성 존에 있는 Eureka 서버를 사용한다.

서비스 레지스트리의 다른 예는 다음과 같다.

  - etcd : 설정의 공유 및 서비스 검색에 사용되는 고 가용적이고 분산되어 있는 일관성 있는 key-value 저장소이다. etcd를 사용하고 있는 주목할만한 2가지 프로젝트는 Kubernetes와 Cloud Foundry이다.
  - consul : 서비스 검색과 설정에 사용하는 도구. 클라이언트가 서비스를 등록하고 검색할 수 있도록 API를 제공한다. consul은 서비스 가용성을 확인하기 위하여 health check를 수행할 수 있다.
  - Apache Zookeeper : 분산 어플리케이션에서 폭넓게 사용되는 고 성능의 coordination 서비스. Apache Zookeeper는 원래 Hadoop의 하위 프로젝트였으나, 지금은 Top-Level 프로젝트이다.

또한, 이전에 언급했듯이, Kubernetes와 Marathon, AWS와 같은 몇몇 시스템에는 명시적인 서비스 레지스트리를 가지고 있지는 않다. 대신에 서비스 레지스트리를 인프라의 일부분으로 내장하고 있다.

지금까지 서비스 레지스트리의 개념에 대해 살펴보았다. 이제 서비스 인스턴스가 서비스 레지스트리에 어떻게 등록되는지를 살펴 보자.
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/768

The Server-Side Discovery Pattern
(서버 측면의 검색 패턴)

서비스를 검색하는 또다른 접근법은 서버 측면의 검색 패턴이다. 다음 다이어그램은 이 패턴의 구조를 보여준다.

사용자 삽입 이미지

클라이언트는 로드 밸런서를 통해 서비스에 요청을 보낸다. 로드 밸런서는 서비스 레지스트리에 질의하고, 이용 가능한 서비스 인스턴스로 각 요청을 라우팅한다. 클라이언트 측면의 검색처럼 서비스 인스턴스들은 서비스 레지스트리에 등록하거나 등록을 취소한다.

AWS(아마존 웹 서비스)의 Elastic Load Balancer(ELB)는 서버 측면의 검색 라우터에 대한 예제이다. ELB는 일반적으로 인터넷의 외부 트래픽 부하를 분산하는데 사용된다. 그러나, 가상 사설 클라우드(VPC) 내부의 트래픽 부하를 분산하는데 ELB를 사용할 수도 있다. 클라이언트는 DNS Name을 사용하여 ELB를 통해 요청(HTTP나 TCP) 한다. ELB는 등록된 Elastic Compute Cloud(EC2) 인스턴스나 EC2 Container Service(ECS) 컨테이너 모음 사이에서 트래픽 부하를 분산시킨다. 별도의 서비스 레지스트리는 없다. 대신, EC2 인스턴스와 ECS 컨테이너들은 ELB 자체에 등록되어 있다.

NGINX Plus와 NGINX와 같은 HTTP 서버와 로드 밸런서들은 서버 측면의 검색 로드 밸런서로 사용될 수도 있다. 예를 들면, 이 블로그 포스트는 Consul Template를 사용하여 NGINX의 Reverse Proxying를 동적으로 재설정하는 부분에 대해 설명하고 있다. Consul Template는 Consul 서비스 레지스트리에 저장되어 있는 설정 데이터를 이용하여 임의의 설정 파일들을 주기적으로 재생성하는 도구이다. 설정 파일들이 변경될 때마다 임의의 쉘 명령어를 실행한다. 블로그 포스트에서 설명된 예제에서 Consul Template는 Reverse Proxying을 설정하는 nginx.conf 파일을 생성한다. 그런 후, NGINX가 설정을 다시 로드하도록 명령어를 실행한다. 보다 더 정교한 구현은 HTTP API나 DNS를 사용하여 NGINX Plus를 동적으로 재구성할 수 있다.

Kubernetes와 Marathon과 같은 일부 배포 환경에서는 클러스터 내의 각 호스트에서 Proxy를 실행한다. Proxy는 서버 측면의 검색 로드 밸런서 역할을 수행한다. 서비스에 요청하기 위하여, 클라이언트는 호스트의 IP 주소와 서비스에 할당된 포트(port)를 사용하여 Proxy를 통해 요청을 라우팅한다. 그런 다음, Proxy는 클러스터 내 어딘가에서 실행 중인 사용 가능한 서비스 인스턴스에 요청을 투명하게 전달한다.

서버 측면의 검색 패턴은 여러 가지 장단점을 가지고 있다. 이 패턴의 한가지 커다른 장점은 검색의 세부사항이 클라이언트로부터 추상화되어 있다는 것이다. 클라이언트는 단순하게 로드 밸런서에게 요청한다. 서비스 클라이언트에서 사용한 프로그래밍 언어와 프레임워크에 대한 검색 로직을 구현할 필요가 없다. 또한, 위에서 언급한 것처럼, 어떤 배포 환경에서는 이 기능을 무료로 제공한다. 그러나 이 패턴은 몇 가지 단점이 있다. 로드 밸런서가 배포 환경에 의해 제공되지 않는 한, 로드 밸런서는 설치하고 관리해야만 하는 또다른 고가용성 시스템 구성 요소이다.
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/767