마이크로서비스 아키텍처 구축 : 5장 마이크로서비스의 통신 구현

일단 해볼게·2025년 11월 9일
0

book

목록 보기
33/34

5.1 이상적인 기술을 찾아서

  • 하위 호환성을 쉽게 하라
    • 마이크로서비스와의 호환성이 깨지지 않도록 해야 한다.
      • 새 필드를 추가하는 작업에 클라이언트가 중단되면 안 된다.
    • 변경 사항이 이전 버전과 호환되는지 검증하는 기능 필요
  • 인터페이스를 명시적으로 만들라
    • 노출하는 기능을 명시적으로 표현하여 마이크로서비스 소비자에게 분명하게 전달
    • 소비자가 기대하는 기능을 명확하게 설명하는 지원 문서와 함께 스키마를 사용해볼 것을 적극 권장
  • API를 기술 중립적으로 유지하라
    • 마이크로서비스를 구현하는 데 사용될 기술 스택을 강요하는 통합 기술은 피해야 한다.
      • 언제 .NET에서 다른 기술로 변할지 모른다.
  • 소비자를 위해 서비스를 단순하게 만들라
    • 소비자가 쉽게 사용하도록 만들자
      • 클라이언트 라이브러리를 제공하면 쉽게 사용할 수 있지만 결합이 증가된다.
  • 내부 구현 세부 사항을 숨겨라
    • 마이크로서비스 내부에서 변경할 때 소비자가 변경하지 않도록 주의하자

5.2 기술 선택

  • 원격 프로시저 호출
    • RPC (remote proedure call)
      • SOAP, gRPC 같은 명시적 스키마 필요
    • 별도의 스키마를 사용하면 다양한 기술 스택을 위한 서버 스텁을 더 쉽게 생성
      • 동일한 WSDL을 정의해 자바 서버, .NET 클라이언트 만들 수 있다.
    • gRPC는 protobuf 사용
      • 일부 구현체는 특정 네트워킹 프로토콜이 제한(HTTP를 반드시 사용하는 SOAP)되어 있는 반면 다른 구현체는 다양한 유형의 네트워킹 프로토콜 사용 가능
    • 명시적 스키마가 있는 RPC 프레임 워크
      • 클라이언트 코드를 생성하기가 쉽다.
      • 그러나 클라이언트가 스키마를 해당 호출 외부에서 가져올 방법이 필요
        • Avro RPC는 페이로드에 전체 스키마를 전송해 클라이언트가 동적으로 스키마를 해석할 수 있게 한다.
    • RPC 문제점
      • 기술 결합 발생
        • 일부 RPC 메커니즘은 클라이언트와 서버에서 사용될 수 있는 기술 제한, 특정 플랫폼에 묶여 있다.
          • RMI를 사용하면 클라이언트, 서버 둘 다 JVM에 종속
            • RMI : 다른 서버에 있는 객체의 메서드를, 마치 내 로컬 객체처럼 호출할 수 있게 해주는 기술
        • 제약 없는 RPC 구현체 : gRPC, SOAP, 스리프트
      • 로컬 호출은 원격 호출과 같지 않다.
        • 로컬 호출은 네트워크를 타지 않기 때문에 네트워크에 대한 비용을 고려해야한다.
        • RPC를 사용하면 페이로드를 마샬링, 역마샬링하는데 상당한 비용이 든다.
        • 네트워크는 신뢰할 수 없다.
          • 클라이언트, 서버가 정상이라도 네트워크는 실패할 수 있다.
          • 속도 일정하지 않고, 패킷 변조 가능
      • 깨지기 쉽다.
        • 아무도 사용하지 않는 필드를 제거하려면 새 버전의 서버를 롤아웃함과 동시에 클라이언트 코드도 변경, 배포해야 한다.
    • 적용 대상
      • gRPC는 HTTP/2 기반으로 효율적이며, Reactive Extension 과도 잘 어울려서 비동기·스트리밍에 강함
      • 하지만 모든 상황에 적합하지 않음. 예를 들어 브라우저나 다양한 플랫폼에서 접근해야 하거나 단순한 API 통신이 필요한 경우에는 REST API가 더 적합
  • REST
    • 리소스
      • 서비스 요청에 따라 Customer에 대한 다양한 표현을 생성
    • 빌딩 블록
      • “요청과 응답 메시지 구조, 메서드, 상태 코드, 헤더, URI” 같은 HTTP 통신의 핵심 구성요소들
    • HATEOAS
      • 클라이언트가 다른 리소스에 대한 링크를 통해 서버와의 상호작용을 수행
    • 문제점
      • 클라이언트 라이브러리가 클라이언트 서버 간의 결합과 관련해 몇 가지 문제를 일으킬 수 있다.
      • HTTP 요청마다 발생하는 오버헤드 (TCP)
        • UDP 또는 QUIC 프로토콜 사용하도록 전환
    • 적용 대상
      • 동기식 요청 및 응답 인터페이스
      • 대규모 요청을 효과적으로 캐싱
      • 비동기적 프로토콜도 가능하지만 마이크로서비스 간 통신에 적합하지 않다.
  • GraphQL
    • 클라이언트가 동일한 정보를 검색하기 위해 여러 번 요청할 필요가 없도록 쿼리 정의
    • GraphQL의 엔드포인트는 클라이언트 쿼리에 대한 진입점, 클라이언트가 사용할 스키마 노출
    • 문제점
      • 클라이언트가 동적 쿼리를 실행할 수 있어 서버에 상당한 부하를 줄 수 있다.
        • 쿼리에 대한 문제를 진단하기 어렵다. 쿼리 플래너 부족
      • HTTP처럼 CDN 같은 중간 캐시가 응답을 캐싱하는 방법 불가능
        • 대안으로, 반환한 모든 리소스에 대해 ID로 연관관계를 만든 후 클라이언트 장치가 해당 ID에 대한 요청을 캐싱한다.
        • 캐시 적중률이 낮을 가능성이 높다.
    • 적용 대상
      • 외부 클라이언트에 기능을 노출하는 시스템의 경계에서 사용하는 데 가장 적합
      • 사용자에게 데이터를 표시하는 제한적인 기능과 모바일 네트워크의 특성 면에서 제약이 있는 모바일 장치에 적합
      • 여러 하위 마이크로서비스에 대한 호출을 집계하는데 사용
      • 대안 : BFF
  • 메시지 브로커
    • 프로세스 사이에 위치해 프로세스 간의 통신을 관리
    • 마이크로서비스 간 비동기 통신에 유용
    • 토픽과 큐
      • 발신자는 큐에 메시지를 넣고 소비자는 해당 큐에서 읽는다.
      • 메시지가 큐를 통해 전송될 때는 전송되는 대상에 대한 정보가 있는 반면 토픽에서는 이 정보가 발신자에게 숨겨지므로 메시지를 누가 받을지 발신자는 알지 못한다.
    • 브로커를 사용하는 이유
      • 전달 보장
        • 메시지를 전달할 수 있을 때까지 보관
          • 다운스트림 대상이 가용하지 않더라도 문제가 되지 않는다.
          • 동기식은 업스트림이 재시도할지, 포기할지 선택해야한다.
      • 신뢰
        • 사용중인 브로커를 얼마나 신뢰할 것인지 결정
      • 다른 특성
        • 메시지 전달 순서
          • 카프카는 개별 파티션 내에서만 순서 보장
        • 쓰기 트랜잭션
          • 카프카는 단일 트랜잭션에서 여러 토픽에 쓸 수 있다.
        • exactly once
          • 메시지에 ID를 포함시키면 처리된 메시지 무시 가능
      • 선택
        • RabbitMQ, ActiveMQ, SQS, SNS, Kinesis, Kafka
      • 카프카
        • 대규모 트래픽 대처 가능
        • 메시지 영속성
          • 메시지 저장 기간 설정
        • 스트림 처리
          • KSQL로 하나 이상의 토픽을 즉석에서 처리하는 SQL과 유사한 명령문 정의 가능

5.3 직렬화 포맷

  • 텍스트 포맷
    • 클라이언트가 자원을 사용하는 방법이 유연해진다.
    • XML
    • JSON
      • 단순한 프로토콜 채택
    • AVRO
      • 스키마를 페이로드의 일부로 전송할 수 있는 기능
  • 바이너리 포맷
    • 페이로드의 크기나 페이로드의 쓰기 및 읽기 효율성이 걱정되면 사용

5.4 스키마

  • 원시 XML → XSD
  • 원시 JSON → JSON 스키마
  • 마이크로서비스의 엔드포인트에 명시적 스키마를 사용하는 방식
    • 엔드포인트가 노출하는 것과 수용할 수 있는 것을 명시적으로 표현하는데 중요한 역할
    • 필요한 문서의 양을 줄이는데 도움
    • 엔드포인트가 우발적으로 파손되는 것을 탐지하는 데 도움이 된다.
  • 계약 위반
    • 구조적 계약 위반
      • 소비자가 더 이상 호환되지 않는 방식으로 엔드포인트의 구조가 변경되는 상황
        • 필드나 메서드 제거, 새로운 필드 추가 등
    • 의미적 계약 위반
      • 구조는 동일하게 유지되지만 소비자의 기대와 다른 방식으로 행동 양식이 변경되는 상황
  • 스키마를 사용해야 할까?
    • 스키마가 명시적인지 여부에 따라 마이크로서비스가 무엇을 노출할 지에 대해 알 수 있다.
    • 스키마는 구조 계약의 일부를 명시적으로 표현하는 데 큰 도움을 준다.
    • 하지만 클라이언트와 서버 모두를 같은 팀이 소유하는 경우처럼 변경 비용이 절감되는 상황에서는 스키마가 없는 것이 더 편하다.

5.5 마이크로서비스 간의 변경 처리

  • 중단 변경을 피하기 위한 방법에 따라 다르다.

5.6 중간 변경 피하기

  • 피하는 방법
    • 확장 변경
      • 마이크로서비스 인터페이스에 새로운 것 추가, 오래된 것 제거하지 않기
    • 관대한 독자
      • 마이크로서비스 인터페이스를 사용할 때 기대하는 것에 유연해야 한다.
      • 클라이언트 코드가 인터페이스와 너무 강하게 바인딩되는 것은 피하고 싶다.
    • 올바른 기술
      • 인터페이스에 하위 호환 가능한 변경 사항을 쉽게 적용하는 기술을 선택하라.
      • gRPC의 Avro는 클라이언트가 동적 타입처럼 페이로드를 해석할 수 있다.
    • 명시적 인터페이스
      • 마이크로서비스가 노출하는 내용은 명확해야 한다.
      • 소비자를 중단시키지 않으려면 어떤 것을 건드리지 말아야 하는지 명확히 알게 된다.
      • 시멘틱 버전 관리를 통해 이전 버전 호환 여부를 알 수 있다.
    • 우발적 중단을 일찍 발견하기
      • 변경 사항이 배포되기 전에 운영 환경에서 소비자를 중단시킬 만한 변경 사항을 찾아내는 메커니즘이 있어야 한다.
      • 스키마 버전을 비교하는 도구를 이용해 구조적 변화를 찾는 데 도움이 될 수 있다.
        • 프로토락, json-schema-diff-validator, 스키마 레지스트리 등

5.7 중단 변경 관리하기

  • 락스텝 배포

    • 인터페이스를 노출하는 마이크로서비스와 이 인터페이스의 모든 소비자를 동시에 변경하는 것이 요구된다.
    • 일회성 상황이고 영향도가 단일 팀으로 제한되면 적절
  • 호환되지 않는 마이크로서비스 버전의 공존

    • 마이크로서비스의 이전 버전과 새 버전을 나란히 실행한다.

    • 이전 소비자의 트래픽을 이전 버전으로 라우팅해 새 소비자가 새 버전을 보게 한다.
      - 넷플릭스가 드물게 사용하는 방식

    • 자주 사용하지 않는 이유

      • 서비스 내부 버그를 수정해야 하는 경우 2개의 서로 다른 서비스를 수정 배포해야 한다.
      • 소비자를 올바른 마이크로서비스로 유도하는 데 지능이 필요하다.
        • nginx로 라우팅
      • 서비스의 두 버전에 의해 생성된 고객은 처음 데이터를 생성하는 데 사용된 버전에 관계없이 저장되고 모든 서비스에 보여질 수 있어야 한다.
        • 복잡성 증가
  • 기존 인터페이스 에뮬레이션

    • 마이크로서비스가 새 인터페이스를 노출하고 기존 인터페이스도 에뮬레이트하도록 한다.
    • 동일 서비스에서 신구 인터페이스를 함께 제공
      • 모든 소비자가 더 이상 구 엔드포인트를 사용하지 않으면 제거
  • 사회적 계약

    • 이전 인터페이스를 운영하는데 비용이 발생해서 빠르게 제거하는 것이 이상적
      • 그러나 소비자는 변경할 시간을 최대한 많이 주길 원한다.
    • 마이크로서비스 소유자 및 소비자 모두 고려해야할 사안
      • 인터페이스를 변경해야 할 이슈는 어떻게 제기할 것인가?
      • 소비자와 마이크로서비스 팀이 변경될 사항에 동의하도록 어떻게 협업할 것인가?
      • 소비자를 업데이트하기 위해 누가 작업을 수행할 것인가?
      • 변경 사항에 동의한다면, 소비자가 인터페이스를 제거하기 전에 새로운 인터페이스로 전환하기까지 얼마나 걸리는가?
  • 사용성 추적

    • HTTP 요청 시 user-agent 헤더에 식별자를 넣는다.
  • 극단적 조치

    • 특정 기간을 고지하고 기간 뒤에 오래된 인터페이스를 폐기
    • 오래된 라이브러리에 sleep을 삽입해 호출에 더 느리게 응답하도록

5.8 마이크로서비스 세계에서 DRY와 코드 재사용의 위험

  • DRY(Don’t Repeat Yourself)
    • 반복하지 말라
    • 시스템 동작과 지식의 중복을 피하자
    • 재사용할 수 있는 코드를 만들자
  • 라이브러리를 통한 코드 공유
    • 사용되는 라이브러리를 한 번에 업데이트할 수 없다.
      • 마이크로서비스 재배포 필요
    • 클라이언트 라이브러리
      • 클라이언트와 서버 간의 결합 주의
      • 서로 독립적으로 서비스를 릴리스하는 기능 유지

5.9 서비스 디스커버리

  • 마이크로서비스가 여러 개 이상인 경우 어디에 무엇이 있는지 파악하는 방법

    • 인스턴스가 스스로 등록하고 알려주는 메커니즘 제공
    • 등록된 서비스를 찾는 방법 제공
  • DNS

    • 서비스 이름 - 도메인 으로 정의하는 관례 기반의 도메인 템플릿을 만들어 확인한다.
      • 예시 > accounts.musiccorp.net
    • 모든 기술 스택이 지원하고 널리 알려진 표준
    • 도메인 엔트리 TTL, DNS 캐싱 등 이전 IP를 참조할 수 있다.
      • 로드 밸런서가 서비스 인스턴스를 가리키게 한다.

    • 동적 서비스 레지스트리
      • 중앙 레지스트리에 등록한 후 등록된 서비스를 조회
      • 주키퍼
        • 구성 관리, 서비스 간 데이터 동기화, 리더 선출, 메시지 큐 등
        • 장점은 노드 간에 데이터가 안전하게 복제되고 노드가 실패할 때 일관성이 유지되도록 하는 데 있다.
      • 콘술
        • 구성 관리와 서비스 디스커버리 지원
        • 핵심 기능 : 기본 제품 기능으로 탑재돼 제공되는 DNS 서버
        • 모든 작업에 REST 기반 HTTP 인터페이스 사용
      • etcd와 쿠버네티스
        • 파드에 컨테이너를 배포한 후 파드와 관련된 메타데이터에 대한 패턴 매칭을 통해 어떤 파드가 서비스에 포함되는지 동적으로 식별

5.10 서비스 메시와 API 게이트웨이

  • 마이크로서비스가 잠재적으로 새로운 클라이언트 라이브러리나 마이크로서비스를 생성하지 않고도 코드를 공유하도록 해준다.

    • 프록시 동작이 개별 마이크로서비스의 특정 동작과 무관해야 한다.
  • API 게이트웨이

    • 외부의 요청을 내부 마이크로서비스로 매핑
    • 대부분 리버스 프록시로 동작
    • API 키, 로깅, 속도 제한 등 메커니즘 구현
    • 적용 대상
      • 사용 사례에 따라 다르다.
        • 쿠버네티스에서 실행되는 마이크로서비스를 노출하는 경우라면 자체 리버스 프록시 도입 가능
    • 회피 대상
      • 호출 집계 또는 필터링
        • GraphQL 사용하면 되는데 API 게이트웨이 계층에서 사용할 이유가 없다.
      • 프로토콜 재작성
        • SOAP API → REST API 변경한다.
          • 너무 많은 동작을 엉뚱한 곳에 밀어 넣기 때문에 중간 계층에서 수행돼서는 안 된다.
      • 모든 마이크로서비스 간 호출의 중개자로 사용하면 안 된다.
        • 하나 이상의 네트워크 홉을 추가한 것이라 지연 시간의 오버헤드가 클 수 있다.
  • 서비스 메시

    • 마이크로서비스 간 통신과 관련된 공통 기능이 메시로 푸시

      • MTLS, 상관관계 ID, 서비스 디스커버리, 로드 밸런싱 등
    • 검증된 공용 라이브러리 재사용을 하기 위해 메시 사용

      • 서로 다른 프로그래밍 언어로 작성된 공통 기능 재사용 가능
    • 공통 기능이 공유 라이브러리를 통해서만 구현됐을 경우 모든 마이크로서비스를 배포할 재배포할 필요없이 통신 측면에서 변경 사항을 유연하게 롤아웃 가능

    • 작동 방식

      • 프록시 간 호출로 인한 영향을 제한하려는 아키텍처를 기반으로 한다.

      • 컨트롤 플레인 : 로컬 메시 프록시 위에 있으며 프록시 동작을 변경하고 프록시 작업에 대한 정보를 수집하는 곳

      • 엔보이 프록시 : 서비스 메시와 다른 종류의 프록시 기반 소프트웨어를 위한 구성 요소로 자주 사용되는 경량 프록시

    • 메시에 삽입하는 공통 동작은 특정 마이크로서비스에 국한되지 않는다.

    • 적합하지 않은 케이스

      • 쿠버네티스를 사용하지 않는 경우
      • 복잡성 가중 (마이크로서비스가 많은 조직에서 필요)
    • API 게이트웨이, 서비스 메시는 HTTP 관련 호출을 처리하는 데 사용

      • 카프카 같은 메시지 브로커 사용은 서비스 메시를 거치지 않는다. → 브로커끼리 직접 통신하기 때문

5.11 서비스 문서화

  • 항상 최신 상태인 것을 보장하고 서비스 엔드포인트의 위치를 알면 API 문서를 쉽게 볼 수 있도록 만들 것이다.
  • 명시적 스키마
    • 쿠버네티스 : 엠버서더의 개발자 포털
      • 새로운 마이크로서비스 배포 후 해당 문서를 자동으로 사용
    • OpenAPI, AsyncAPI, CNCF
  • 자기 기술 시스템
    • UDDI와 같은 표준이 등장해 어떤 서비스가 실행되고 있는지 이해하는 데 도움이 됐다.
      • 상당히 무거워서 대체제 필요
    • 휴먼 레지스트리 개념 논의
      • 호출 체인 확인을 위한 상관관계 ID, 서비스 디스커버리 등
profile
시도하고 More Do하는 백엔드 개발자입니다.

0개의 댓글