⏱️ 동기와 비동기
동기(Synchronous)
- 한 작업이 완료될 때까지 대기한 후 다음 작업을 수행하는 방식
- 하나의 요청이 끝나기 전까지 다음 처리를 하지 못합니다.
- 특정 작업이 오래 걸릴 경우 다른 작업이 블로킹(Blocking) 됨
- 순차적으로 실행되므로 코드의 실행 흐름을 예측하기 쉽지만, 속도가 느릴 수 있음
비동기(Asynchronous)
- 한 작업을 실행하는 동안 다른 작업을 동시에 수행하는 방식 (대기하지 않음)
- 요청을 보낸 뒤 응답을 기다리지 않고 바로 다음 작업을 진행
- 시간이 오래 걸리는 작업(예: API 호출, 데이터베이스 조회, 파일 처리)을 비동기로 처리하면 성능이 향상됨
비교
동기 | 비동기 | |
작업 처리 방식 | 순차적으로 실행 | 동시에 여러 작업 가능 |
응답 대기 여부 | 요청이 끝날 때까지 대기 | 결과를 기다리지 않고 즉시 다음 코드 실행 |
성능 | 속도가 상대적으로 느림 | 대규모 트래픽에서 성능 향상 |
사용 방식 | 일반 메서드 호출 | Java의 경우 `@Async`, `CompletableFuture`, `ExecutorService`, `Reactor`(`WebFlux`) 등을 이용하여 비동기 처리 |
사용 예시 | - 작업의 순서가 중요할 때 - 각 단계의 결과가 다음 단계에 즉시 필요할 때 - 간단한 스크립트나 작은 규모의 프로그램 |
- 네트워크 요청이나 파일 I/O와 같은 시간이 오래 걸리는 작업을 처리할 때 - 여러 독립적인 작업을 동시에 처리해야 할 때 - 사용자 인터페이스의 반응성을 유지해야 할 때 - 대규모 동시성이 필요한 서버 애플리케이션 |
📦 RabbitMQ를 활용한 비동기 처리 구조
Java 내부 비동기(`@Async` 등)과 RabbitMQ 비교
Java 내부 비동기 (`@Async`, `CompletableFuture` 등) |
메시지 큐 기반 비동기 (RabbitMQ) | |
작업 실행 위치 | JVM 내부 스레드 (동일 프로세스) | 외부 브로커(RabbitMQ)를 통해 별도 Consumer 프로세스 |
비동기 트리거 | 코드 상에서 메서드를 직접 비동기로 실행 | 메시지를 큐에 publish 후, 리스너가 비동기 처리 |
목적 | 주로 단기적 비동기 작업 (ex. 병렬 처리) | 시스템 간 비동기 통신 및 이벤트 처리 |
결과 처리 방식 | 반환값으로 `CompletableFuture` 등 활용 | 메시지 처리 후 후속 로직 또는 저장 (보통 리턴 없음) |
내결함성 | JVM 내 예외 발생 시 앱에 영향 | DLQ, 재시도, 큐 모니터링 등으로 높은 복원력 확보 가능 |
확장성 | 스레드풀/리액터 기반 확장 | MQ 수평 확장으로 고성능 처리 가능 (멀티 인스턴스 대응) |
정리 | @Async, CompletableFuture 등은 애플리케이션 내부 병렬성 향상 도구 | RabbitMQ는 서비스 간 비동기 메시지 전달 및 아키텍처 구성 도구 |
구성 요소
- Producer: 메시지를 Queue에 전송하는 주체
- Queue: 메시지를 임시로 저장하고, Consumer가 가져가길 기다림
- Consumer: Queue로부터 메시지를 수신하고 처리
- Exchange: 메시지를 받아서 라우팅 규칙에 따라 적절한 Queue로 전달
🔔 실제 구현 예시: 채팅 알림 비동기 처리
`table-now` 프로젝트에서는 RabbitMQ를 통해 채팅 알림, 예약 리마인드, 이벤트 알림 등을 메시지 큐 기반 비동기 처리했습니다.
1. 채팅 메시지 전송
rabbitTemplate.convertAndSend(
CHAT_EXCHANGE,
CHAT_ROUTING_KEY,
response
);
→ 사용자가 채팅 메시지를 보내면 서버는 메시지를 큐에 넣고, 즉시 응답을 반환
→ 이후 실제 알림 전송은 별도의 Consumer가 처리
2. 메시지 수신 및 알림 생성
@RabbitListener(queues = CHAT_QUEUE)
public void consume(ChatMessageResponse chatMessage) {
// 생략
try {
notificationService.createNotification(
NotificationRequestDto.builder()
.userId(receiverId)
.type(NotificationType.CHAT)
.content(buildChatContent(chatMessage))
.build()
);
// 생략
} catch (Exception e) {
// 생략
}
}
- 수신자는 `@RabbitListener`를 통해 큐에 쌓인 메시지를 처리
- (이 구조로 실제 알림 생성이 느려도 사용자 응답에는 영향이 없다.)
3. 실패 대응: DLQ + 재시도 처리
throw new AmqpRejectAndDontRequeueException("[DLQ] 알림 전송 실패 → DLQ로 이동", e);
- 메시지 처리 실패 시 Dead Letter Queue(DLQ)로 이동
- DLQ에서는 Retry Count를 기준으로 최대 3회까지 재시도 (x-retry-count)
→ 유실 없는 메시지 처리 구조
https://github.com/spring-team-7/table-now/pull/199/commits/c7f8eff16f1f17ae42145cb9306198c2c274cd40
[feat] WebSocket + RabbitMQ 기반 채팅 기능 고도화 by mannaKim · Pull Request #199 · spring-team-7/table-now
🔗 Issue Number close #189 📝 작업 내역 Spring WebSocket + STOMP 메시지 Relay를 RabbitMQ로 전환 이전 구조 (SimpleBroker)에서는 → 서버(Spring)가 직접 클라이언트에게 메시지를 브로드캐스트 지금 구조 (StompBrok
github.com
https://github.com/spring-team-7/table-now/pull/268
[feat] 채팅 알림 DLQ / DLX 적용 by mannaKim · Pull Request #268 · spring-team-7/table-now
🔗 Issue Number close #257 📝 작업 내역 RabbitConstant에 CHAT_DLX, CHAT_DLQ 상수 추가 RabbitConfig에 chat DLX, DLQ, 바인딩 설정 추가 ChatDlqReprocessor 클래스 생성 ChatRetryService 클래스 생성 및 재처리 로직 구현 💡
github.com
📝 비동기 처리 방식 정리
상황 | 추천 방식 |
단순한 비동기 작업 (ex. 비동기 메일 전송) | @Async, CompletableFuture |
사용자 응답과 무관한 처리 | RabbitMQ |
재시도/지연 처리 필요 | RabbitMQ + Retry Queue |
RabbitMQ를 활용한 비동기 이벤트 처리 구조는 다음과 같은 장점을 가진다:
- 사용자 응답 속도 향상
- 처리 실패에 대한 유연한 대응 (DLQ, Retry)
'TIL (Today I Learned)' 카테고리의 다른 글
Spring Boot 면접 대비 개념 정리❕ (1) | 2025.06.19 |
---|---|
JVM의 개념과 내부 구조 정리 (3) | 2025.06.19 |
JWT 인증 기반 WebSocket 연결 실패, 원인과 해결법 (2) | 2025.06.05 |
Spring Boot 설정값 바인딩 정리: @Value vs @ConfigurationProperties (2) | 2025.05.24 |
동시성 제어는 어떻게 할까❓ (0) | 2025.03.26 |