본문 바로가기

카테고리 없음

비동기 메시징 패턴 응용 통신(Microservices Patterns)

마이크로서비스 패턴(Microservices Patterns) by Chris Richardson 책의 3.3장 비동기 메시징 패턴 응용 통신을 개괄적으로 정리한 내용입니다.

 

메시징은 서비스가 메시지를 서로 비동기적으로 주고 받는 통신 방식이다.

 

메시지 구성 요소

  • 메시지
    • header
      • 송신된 데이터에 대한 metadata에 해당하는 key-value로 구성
      • messageId(송신자 또는 메시징 인프라에서 생성)
      • 응답이 출력될 메시지 채널을 가리키는 반환 주소(option)
    • body
      • 실제로 송신할 텍스트 또는 이진 포맷의 데이터
    • 종류
      • 문서(document)
        • 데이터만 포함한 제네릭한 메시지(e.g. 커맨드에 대한 응답)
        • 메시지를 어떻게 해석할지는 수신자가 결정
      • 커맨드(command)
        • RPC 요청과 동등한 메시지
        • 호출 작업과 전달할 매개변수가 지정되어 있음
      • 이벤트(event)
        • 송신자에게 어떤 사건이 발생했음을 알리는 메시지
        • 이벤트는 대부분 Order, Customer 같은 도메인 객체의 상태 변화를 나타내는 도메인 이벤트
  • 채널
    • 송신자
      • 송신자의 비즈니스 로직은 하부 통신 메커니즘을 캡슐화한 송신 포트 인터페이스 호출
      • 메시지 송신자 어댑터 클래스로 구현
      • 이 클래스는 메시징 인프라를 추상화한 메시지 채널을 통해 수신자에게 메시지 전달
    • 수신자 메시지 핸들러(handler, 처리기) 어댑터 클래스
      • 메시지 처리하기 위해 호출
      • 컨슈머 비즈니스 로직으로 구현된 수신 포트 인터페이스 호출
    • 송신자가 채널에 보낼 수 있는 메시지와 수신자가 채널에서 받을 수 있는 메시지 갯수는 무제한
    • 종류
      • point-to-point 채널
        • 채널을 읽는 컨슈머 딱 하나만 지정하여 메시지 전달
        • 일대일 상호 작용 스타일의 서비스가 이 채널 사용(e.g. command messgage)
      • publish-subscribe 채널
        • 같은 채널을 바라보는 모든 컨슈머에 메시지 전달
        • 일대다 상호 작용 스타일 서비스가 사용(e.g. 이벤트 메시지)

 

메시징 상호 작용 스타일 구현

  • 요청/응답 and 비동기 요청/응답
    • messageId <-> correlationId
  • 단방향 알림(one-way notification)
    • 서비스는 응답 미반환
  • 발행/구독
    • 서비스가 도메인 이벤트 발행
    • 해당 도메인 클래스의 이름을 딴 발행/구독 채널을 소유
    • (주문 서비스는 Order 이벤트를 Order 채널에 발행)
    • 서비스는 자신이 관심있는 도메인 객체의 이벤트 채널을 구독
  • 발행/비동기 응답
    • 발행/구독과 요청/응답의 엘리먼트를 조합한 고수준의 상호 작용 스타일
    • 클라이언트
      • 응답 채널 헤더가 명시된 메시지를 발행/구독 채널에 발행
    • consumer
      • correlationId가 포함된 응답 메시지를 지정된 응답 채널에 씀

 

메시징 기반 서비스의 API 명세 작성

  • 구성 요소
    • 메시지 채널명
    • 각 채널 통해 교환되는 메시지 타입과 포맷
    • 메시지 포맷은 JSON, XML, 프로토콜 버퍼 등 표준 포맷으로 기술
  • 비동기 작업 문서화
    • 요청/비동기 응답 스타일 API
      • 서비스의 커맨드 메시지 채널
      • 서비스가 받는 커맨드 메시지의 타입과 포맷
      • 서비스가 반환하는 응답 메시지의 타입과 포맷
    • 단방향 알림 스타일 API
      • 서비스의 커맨드 메시지 채널
      • 서비스가 받는 커맨드 메시지의 타입과 포맷
  • 발행 이벤트 문서화
    • 발행/구독 스타일로도 이벤트 발행 가능
    • 구성요소
      • 이벤트 채널
      • 서비스가 채널에 발행하는 이벤트 메시지의 타입과 포맷

 

메시징 기술

  • brokerless 메시징
    • 서비스 간 메시지를 직접 교환
    • ZeroMQ 등
    • TCP, 유닉스형 domain socket, multicast 등 다양한 전송 기술 지원
    • 장점
      • 네트워크 트래픽이 가볍고 지연 시간이 짧음
      • 메시지 브로커가 성능 병목점이나 SPOF(Single Point Of Failure)가 될 일이 없음
      • 메시지 브로커를 설정/관리할 필요가 없으므로 운영 복잡도가 낮음
    • 단점
      • 서비스가 서로의 위치를 알아야 하므로 서비스 디스커버리 매커니즘 중 하나를 사용해야 함
      • 메시지 교환 시 송신자/수신자 모두 실행중이어야 하므로 가용성 떨어짐
      • 전달 보장(guarantted delivery) 같은 메커니즘을 구현하기가 더 어려움
  • 메시지 브로커
    • 정의
      • 서비스가 서로 통신할 수 있게 해주는 인프라 서비스
      • 모든 메시지가 지나가는 중간 지점
    • 장점
      • 송신자가 컨슈머의 네트워크 위치를 몰라도 됨
      • 컨슈머가 메시지를 처리할 수 있을 때까지 메시지 브로커에 메시지를 버퍼링 가능
    • 제품
      • ActiveMQ
      • RabbitMQ
      • Apache Kafka
      • AWS Kinesis, AWS SQS 등 클라우드 기반 메시징 서비스도 있음
    • 제품 선택 시 검토 사항
      • 프로그래밍 언어 지원 여부
      • 메시징 표준 지원 여부
        • AMQP, STOMP 등 표준 프로토콜을 지원하는 제품인가, 아니면 자체 표준만 지원하는가?
      • 메시지 순서
      • 전달 보장
      • 영속화
      • 내구성
        • 컨슈머가 메시지 브로커에 다시 접속할 경우, 접속이 중단된 시간에 전달된 메시지 받을 수 있는지
      • 확장성
      • 지연 시간
      • 경쟁사 컨슈머

 

메시지 브로커로 메시지 채널 구현

  • 제품
    • ActiveMQ(JMS 메시지 브로커) -> queue, topic
    • RabbitMQ(AMQP 기반 메시지 브로커) -> exchange, queue
    • Apache Kafka -> topic
    • AWS Kinesis -> stream
    • AWS SQS -> queue
  • 장점
    • 느슨한 결합
    • 메시지 버퍼링
    • 유연한 통신
    • 명시적 IPC
  • 단점
    • 성능 병목 가능성
    • 단일 장애점 가능성
    • 운영 복잡도 증가

 

메시징 처리 고려 사항

  • 수신자 경합과 메시지 순서 유지
    • Apache Kafka, AWS Kinesis
      • sharded 채널 이용
    • 솔루션 구성
      • 샤딩된 채널은 복수의 샤드로 구성되며, 각 샤드는 채널처럼 작동
      • 송신자는 메시지 헤더에 샤드 키(보통 무작위 문자열 or byte)를 지정. 메시지 브로커는 메시지를 샤드 키별로 샤드/파티션에 배정
      • 메시징 브로커는 여러 수신자 인스턴스를 묶어 마치 동일한 논리 수신자처럼 취급(apache kafka에서의 consumer group). 메시지 브로커는 각 샤드를 하나의 수신자에 배정하고 수신자가 시동/종료하면 샤드를 재배정
  • 중복 메시지 처리
    • 클라이언트, 네트워크, 메시지 브로커 자신이 실패할 경우 같은 메시지를 여러 번 전달할 수도 있음
    • 방법1(멱등한(idempotent) 메시지 핸들러 작성)
      • 멱등하면 좋지만, 그런 서비스는 많이 없음
    • 메시지를 추적하고 중복을 솎아냄
      • 컨슈머가 messageId 이용해 메시지 처리 여부를 추적하면서 중복 메시지 솎아내면 간단히 해결
      • 컨슈머는 메시지를 처리할 때 비즈니스 엔티티를 생성/수정하는 트랜잭션의 일부로 messageId를 DB 테이블에 기록
        • 전용 테이블에 messageId가 포함된 row를 삽입하고, 중복된 메시지라면 insert 쿼리가 실패
      • 전용 테이블 대신 일반 애플리케이션 테이블에 messageId 기록
      • (한 DB 트랜잭션으로 두 테이블을 업데이트하는 일이 불가능한, 트랜잭션 모델이 제한적인 NoSQL DB를 쓸 때 유용)

 

트랜잭셔널 메시징

  • RDB
    • DB 테이블을 메시지 큐로 활용
    • transactional outbox pattern
  • NoSQL
    • DB에는 레코드로 적재된 비즈니스 엔티티를 발행할 메시지 목록을 가리키는 속성이 존재
    • 서비스가 DB entity를 업데이트할 때 이 목록에 메시지를 덧붙임
    • 단일 DB 작업이므로 원자적이지만, 문제는 이벤트를 가진 비즈니스 엔티티를 효과적으로 찾아 발행해야 함

 

메시지를 DB에서 메시지 브로커로 옮기는 방법 2가지

  • 이벤트 발행(polling 발행기 패턴)
    • message relay로 테이블을 polling해서 미발행 메시지를 조회하는 것
    • select * from outbox order by ... asc 쿼리를 주기적으로 실행
      • 조회 메시지를 하나씩 각자의 목적지 채널로 보내서 메시지 브로커에 발행
      • 나중에 outbox 테이블에서 메시지 삭제
    • 장점
      • 규모가 작을 경우 쓸 수 있는 단순한 방법
    • 단점
      • DB를 자주 폴링하면 비용 유발
      • outbox 테이블 쿼리 대신 비즈니스 엔티티를 쿼리해야 하면, 효율적일 수도 불가능할 수도 있음
  • 이벤트 발행(transaction log tailing)
    • message relay로 DB 트랜잭션 로그(commit log)를 tailing 하는 방법
    • 커밋된 업데이트는 각 DB의 트랜잭션 로그 항목으로 남는데, transaction log miner로 트랜잭션 로그를 읽어 변경분을 하나씩 메시지로 메시지 브로커에 발행
    • 사례
      • Debezium
        • DB 변경분을 아파치 카프카 메시지 브로커에 발행하는 오픈소스 프로젝트
      • LinkedIn Databus
        • 오라클 트랜잭션 로그를 마이닝해서 변경분을 이벤트로 발행하는 오픈소스 프로젝트
      • DynamoDB streams
        • 최근 24시간 동안 테이블 아이템에 적용된 변경분을 시간 순으로 정렬된 데이터 가지고, 애플리케이션은 스트림에서 변경분을 읽어 이벤트로 발행
      • Eventuate Tram
        • MySQL binlog 프로토콜, Postgres WAL 폴링을 응용해서 outbox 테이블의 변경분을 읽어 아파치 카프카로 발행

 

메시징 라이브러리/프레임워크

  • 서비스가 메시지 주고 받으려면 라이브러리 필요
  • 메시지 브로커에도 클라이언트 라이브러리가 있지만 직접 사용하면 다음의 문제 발생
    • 메시지 브로커 API에 메시지를 발행하는 비즈니스 로직이 클라이언트 라이브러리와 결합됨
    • 메시지 브로커의 클라이언트 라이브러리는 대부분 저수준이고 메시지 주고받는 코드가 꽤 긴 편
    • 메시지 브로커의 클라이언트 라이브러리는 기본적인 메시지 소통 수단일 뿐, 고수준의 상호 작용 스타일은 지원하지 않음
  • 저수준 세부사항을 감추고 고수준의 상호 작용 스타일을 직접 지원하는 고수준 라이브러리/프레임워크가 필요

 

비동기 메시징으로 가용성 개선

동기 통신

  • 단점
    • REST는 대중적인 IPC지만, 동기 프로토콜이라는 치명적 문제점
    • 모든 서비스가 가동 중이어야 함(가용성)

동기 상호 작용 제거

  • 비동기 상호 작용 스타일
    • 클라이언트가 요청하는 채널의 A서비스가 B,C,D 등 다른 서비스와 메시지를 비동기 방식으로 교환
    • 최종적으로 클라이언트에 응답 메시지 전송
    • 메시지가 소비되는 시점까지 메시지 브로커가 메시지를 버퍼링하기 때문에 매우 탄력적
    • 서비스에 동기 API가 있는 경우 데이터를 복제하면 가용성 높일 수 있음
  • 데이터 복제
    • 서비스 요청 처리에 필요한 데이터의 레플리카를 유지하는 방법
    • 데이터 레플리카는 데이터를 소유한 서비스가 발행하는 이벤트를 구독해서 최신 데이터 유지 가능
    • 예를 들어 주문 서비스는 소비자/음식점 데이터의 레플리카를 갖고 있으므로 자기 완비형
    • 개선사항
      • 엄청난 양의 데이터를 그대로 복제하는 것은 실용적이지 않음. 다른 서비스가 소유한 데이터를 업데이트 하는 문제도 데이터 복제만으로 해결되지 않음
      • 다른 서비스와의 상호 작용을 지연시켜서 개선(응답 반환 후 마무리)
        • 로컬에서 가용한 데이터만 갖고 요청을 검증
        • 메시지를 outbox 테이블에 삽입하는 식으로 DB를 업데이트
        • 클라이언트에 응답을 반환
      • 내용
        • 서비스는 요청 처리 중에 다른 서비스와 동기적 상호작용하지 않고, 다른 서비스에 메시지를 비동기 전송함
        • 예시
          • 클라이언트가 주문 요청
          • 주문 서비스는 주문을 PENDING 상태로 생성
          • 주문 서비스는 주문 ID가 포함된 응답을 클라이언트에 반환
          • 주문 서비스는 ValidateConsumerInfo 메시지를 소비자 서비스에 전송
          • 주문 서비스는 ValidateOrderDetails 메시지를 음식점 서비스에 전송
          • 소비자 서비스는 ValidateConsumerInfo 메시지를 받고 주문 가능한 소비자인지 확인 후, ConsumerValidated 메시지를 주문 서비스에 보냄
          • 음식점 서비스는 ValidateOrderDetails 메시지를 받고 올바른 메뉴 항목인지 음식점에서 주문배달지로 배달이 가능한지 확인 후, OrderDetailsValidated 메시지를 주문 서비스에 전송
          • 주문 서비스는 ConsumerValidated 및 OrderDetailsValidated를 받고 주문 상태를 VALIDATED로 변경
          • 클라이언트에 상태 반환