-
메시지 큐와 비동기 통신spring 2024. 7. 1. 18:49
분산 시스템, 마이크로서비스 아키텍처를 공부하며 빠질 수 없는 개념인 메시지 큐에 대해 정리하고자 한다.
메시지 큐의 장점
- 디커플링(decopuling)
Synchronous 한 통신은 높은 결합도를 가진다.
위 다이어그램의 시스템은 높은 결합도를 가진 시스템의 예시이다.
이러한 형태는 하나의 서비스라도 실패할 경우 모든 서비스가 실패하게 된다.비동기 메시징으로 통신하면 서비스 간의 결합도를 감소시킬 수 있다.
(ServiceA는 메시지 큐에 발행하는 것으로 책임을 다한다.)- 성능 최적화
Synchronous한 서비스는 요청 - 응답에 필요하지 않은(관심사가 아닌) 작업들까지 기다려야 한다.
작업을 A ~ D 까지 수행하고, 각 작업 당 500ms가 걸린다고 가정했을 때 clientA는 2000ms의 Latency가 발생한다.
하지만 clientA는 A와 D 외의 작업에 관심이 없다.비동기 메시징을 적용한 Asynchronous한 서비스는 이와 같이 관심사 별로 작업을 분리하여 서로의 시간을 절약하는 효과를 얻을 수 있다.
- 트래픽 완충제 역할
서비스의 특성에 따라 Peak-Time이 존재할 수 있고, 일시적으로 급증하는 트래픽이 발생할 수 있다.
AutoScaling를 적용하면 괜찮지 않을까 하는 생각이 들 수 있지만, AutoScaling은 단 시간에 급증하는 트래픽의 대응책으로는 부실한 면이 있다.- Scaling 과정에 걸리는 시간 만큼 서비스의 과부화가 지속될 수 있다.
- 자동 확장은 클라우드 리소스를 더 많이 사용하여 비용 효율적이지 못하다.
비동기 통신을 도입하면 위의 한계를 극복하고, 시스템의 성능과 유연성을 높일 수 있다.
- 즉각적인 부하 분산
- 비동기 메시지 큐를 사용하면, 메시지를 큐에 넣고 즉시 다음 작업을 수행할 수 있다.
- 큐에 쌓인 메시지는 시간이 걸리더라도 천천히 처리될 수 있다.
- 피크 부하 흡수
- 메시지 큐는 트래픽 피크를 흡수하는 완충 역할을 한다.
- 트래픽이 급증해도 메시지가 큐에 쌓이고, 컨슈머가 처리할 수 있는 속도로 메시지를 처리한다. 이는 시스템이 일시적인 과부하로 인해 다운되지 않도록 한다.
- 유연한 확장성
- 컨슈머를 쉽게 추가하여 메시지 처리 용량을 늘릴 수 있다.
- 컨슈머를 동적으로 늘리거나 줄임으로써, 시스템의 처리 용량을 유연하게 관리할 수 있다.
메시지 큐를 사용할 때 주의해야 할 점
- 시스템 복잡성 증가(Complexity)
- 데이터 일관성, 전송 전달 보증, 영속성과 내구성, 순서, 필터링과 라우터링, 가용성, 확장성, 성능 등 메시지 큐를 도입하려면 개발자의 많은 고민이 필요하며 그에 따른 코드의 복잡도도 증가한다.
- 데이터 일관성 문제(Consistency)
- 분산 시스템에서 데이터 일관성을 유지하는 방법으로는 분산 트랜잭션과 궁극적 일관성이 있다.
- 분산 트랜잭션은 사용되지 않으며, 궁극적 일관성을 위한 방법으로 Saga 패턴이 가장 많이 사용된다.
- Saga 패턴은 비동기 메시징을 활용하여 여러 서비스들의 로컬 트랜잭션을 순차적으로 실행되게 함으로써 데이터 일관성을 유지하는 방법이다.
- Saga 패턴은 다시 Choreography-based와 Orchestration-based 두 가지 방법으로 나뉜다. 서비스의 규모에 따라 두 방법 중 하나를 선택할 수 있다. 이에 대한 설명은 너무 길어지기에 생략한다.
- 전송, 전달 보증(Delivery-Guarantee)
- Delivery-Guarantee에는 exactly-once(정확히 한번), at-least-once(최소 한번), at-most-once(최대 한번)로 총 세가지 종류가 있다.
- 중복 또는 다중 쓰기, 입력 레코드 다시 읽기, partial failure, network failure, 패킷 유실, ack 유실 등의 여러 요인으로 exactly-once가 사실상 best-effort이다.
- exactly-once를 지원하는 메시지 큐는 Apache Kafka 나 AWS SQS 가 있다.
- exactly-once라는 개념을 비관적으로 본다면 at-most-once 혹은 at-least-once를 적용할 수 있다.
- at-most-once의 경우는 메시지가 more than once일 경우, at-least-once의 경우는 never일 경우에 서비스에서 직접 대비할 지(혹은 메시지 큐에 어떤 책임을 위임할지) 결정해야 한다.
- 요구 사항을 면밀히 분석하여 어떤 delivery-guarantee를 적용할지 고민해야 한다.
- 영속성(Persistence)
- 메시지 큐의 영속성은 메시지를 처리하는 도중 서비스 failure 발생 시, 메시지 보존 여부를 의미한다.
- 메시지 큐 Persistence의 대상은 메시징 플랫폼에 따라 호스트의 특정 폴더, 데이터베이스, 로그파일 등이 될 수 있다.
- 트레이드오프는 성능이다. 메시지를 어딘가에 보관하고 메모리의 메시지와 상태를 일치시키기 위해서 추가적인 I/O 가 발생하고 로직이 필요하다.
- Persistence는 메시지 유실이 용인되는 정도에 따라 적용 여부를 결정하라.
- 내구성(Durability)
- Durability를 적용하면 일시적으로 queue가 offline된 상태일 때 발행된 메시지를, queue가 다시 복구 되었을 때 전달할 수 있게 된다.(Durability를 적용하지 않은 메시지 큐는 위와 같은 상황에서 발행된 메시지가 유실된다.)
- Durability 또한 메시지 유실이 용인되는 정도에 따라 적용 여부를 결정하라.
- 순서(Ordering)
- 메시지 큐는 작업 완료 순서를 보장하지 않기 때문에 발행 순서에 상관없이 Consumber의 성능에 따라 다른 순서로 소비될 수 있다.
- 결제와 같은 처리 순서가 중요한 서비스에 메시지 큐를 적용할 시 Ordering 기능을 제공하는지, 어느 범위까지 제공하는지, 어떻게 제공하는 지를 살펴봐야 한다.
- 그에 따른 성능 비용과 로직의 복잡도를 고려할 필요가 있다.
- 필터링, 라우팅(Filtering & Routing)
- 조건적인 publishing, subscription 이 필요한 경우 필터링과 라우팅을 사용한다. 메시지 헤더 또는 바디에 value에 따라 필터링을 해야 할 수도 있고, queue틀을 계층 구조로 구성해야 할 수도 있다.
- 다음은 메시지 큐 적용 시 고려할 수 있는 필터링/라우팅 옵션들이다.
- 가벼운, Stateless 한 방식
- 큐 계층 기반
- topic 단위
- routing key, 또는 binding key
- Queue 단위
- Routing Key 또는 Binding Key 값
- Message Header
- Message Body
- 의외에도 여러 메시지 라우팅/필터링 방식이 존재한다.
'spring' 카테고리의 다른 글
Spring WebFlux와 Reactive Programming (0) 2024.07.01 페이징 요청을 받는 두가지 방법 (0) 2024.03.11 application.properties 가 merge 되지 않도록 하는 방법 (0) 2024.02.26 도메인이란 (1) 2023.12.05 Entity, DTO의 개념과 차이 (0) 2023.12.04