비동기 프로그래밍, 얼마나 알고 있나요? (Advanced Asynchronized)
이벤트 기반 IO 처리 방식의 비동기 설계 패턴으로 여러 방식이 존재하고, 비동기 작업을 관리하지만 그 방식이 상이하다.
비동기 방식의 구분 : 이벤트를 감지하는 주체가 누구인가?
두 방식 모두 "-actor"로끝나며 행동하는 주체로서 행위자(actor)를 의미한다. 따라서 어떤 방식으로 행동하는지를 나타낸다고 볼 수 있다.
- Reactor : IO 이벤트가 발생하면 직접 반응(React)하여 실행
- Proactor : 미리, 앞서서(Pro) + IO 작업을 요청해두고, 완료되면 OS가 자동으로 실행
1. Reactor 패턴
I/O 이벤트가 발생하면 핸들러(Callback)를 등록해 두고, 이벤트가 발생하면 이를 처리하는 방식
동작 방식
- 애플리케이션이 비동기 작업을 요청하고, 처리할 핸들러(콜백)를 등록
- I/O 멀티플렉싱(select, poll, epoll 등)을 사용하여 이벤트를 감시
- 이벤트가 발생하면, Reactor가 적절한 핸들러를 실행하여 데이터를 처리
특징
- 이벤트 기반: 이벤트가 발생하면 이를 즉시 처리할 수 있도록 콜백이 실행됨
- I/O 작업은 여전히 블로킹이지만, 핸들러는 비동기적으로 실행
- 싱글 스레드에서도 동작 가능, 확장 가능
- 대표적인 예시:Java NIO, Node.js
장단점
- 여러 I/O 이벤트를 동시에 감지하고 처리할 수 있어 고성능
- 비교적 구현이 간단하고 제어가 쉬움
- 콜백 지옥(Callback Hell)
- 핸들러가 너무 오래 실행되면, 다른 이벤트 처리가 지연 가능성
2. Proactor 패턴
I/O 작업을 운영체제(커널)에 맡기고, 완료되었을 때 비동기적으로 알림을 받는 방식
Proactor는 OS가 내부적으로 비동기 입출력 처리하고 알려주는거라 OS의 지원이 필수적이다
동작 방식
- 애플리케이션이 비동기 I/O 요청을 보내고, 완료 핸들러(Completion Handler)를 등록
- OS가 I/O 작업을 수행하고 완료되면, 완료 이벤트를 Proactor에게 전달
- Proactor가 적절한 핸들러를 실행하여 결과를 처리
특징
- OS가 직접 비동기 I/O를 관리 (Windows의 IOCP)
- CPU와 I/O 작업이 병렬적으로 수행 가능
장단점
- OS가 직접 I/O 처리를 담당하여 고성능
- I/O 작업이 완료된 후에만 핸들러가 실행되므로 불필요한 반복 체크가 필요 없음
- 플랫폼 종속적
- 구현이 복잡하고 디버깅이 어려울 수 있음
3. Reactor vs Proactor 비교
패턴 | 방식 | I/O 작업 수행 | 적합한 환경 | 구현 |
Reactor | 이벤트 기반 | 애플리케이션이 이벤트 감지 후 처리 | 이벤트 루프 | Java NIO, epoll |
Proactor | 완료 기반 | OS가 직접 처리하고 완료 이벤트 전달 | 고성능 서버 | Windows IOCP |
- Reactor는 이벤트 기반으로 I/O를 직접 관리하는 방식
- Proactor는 OS가 I/O를 처리하고, 완료 이벤트를 받아 처리하는 방식
비동기 방식 구분 : 이벤트를 어떻게 처리할 것인가?
1. 멀티플렉싱 기반 비동기 I/O (Polling 방식)
멀티플렉싱 방식에서는 하나의 스레드가 여러 I/O 상태를 감시하면서, 처리할 준비가 된 I/O를 감지하면 실행하는 방식
방식 | select() | epoll() |
성능 | FD 개수 증가에 따라 성능 저하 (O(N)) | 이벤트 기반 (O(1)) |
방식 | 폴링 기반 (반복 검사) | 이벤트 기반 (OS가 감지한 후 알림) |
확장성 | 수천 개 이상의 FD에서 성능 문제 | 수십만 개의 FD도 효율적으로 처리 가능 |
2. 콜백 기반 비동기 I/O (OS 알림 방식)
콜백 기반 방식에서는 I/O 요청을 보낸 후, OS가 완료되면 자동으로 콜백을 호출하는 방식
IOCP (IO Completion Port)
- WSARecv()는 I/O 요청을 보내고 즉시 리턴
- I/O가 완료되면 OS가 IOCP 큐에 이벤트를 추가 (GetQueuedCompletionStatus()에서 이벤트 감지)
- 완전히 논블로킹이며, OS가 직접 완료된 이벤트를 전달하므로 CPU 낭비가 없음
IOCP(IO Completion Port) 쉽게 이해하기
IOCP(IO Completion Port) 란?Windows 운영체제에서 제공하는 비동기 I/O 처리 모델게임 서버에서 사용해서 착각하는데, 소켓 네트워크 기술이 아니라 이벤트 기반 I/O 처리 방법론이다.Linux는 소켓을 파
downfa11.tistory.com
3. 멀티 플렉싱 vs 콜백 비교
방식 | 이벤트 감지 방식 | 대표 기술 | 특징 |
멀티플렉싱 | 사용자 스레드가 I/O 상태를 직접 감지 | select(), poll(), epoll() | 이벤트 루프 |
콜백 기반 | OS가 직접 완료된 I/O를 알려줌 | IOCP, APC (Windows) | OS가 직접 이벤트를 전달 |
그럼 Java는요..?
대부분의 개발자들은 아마 웹 프레임워크 수준에서의 비동기 처리에 익숙할거다. Java는 기본적으로 Proactor 패턴을 지원하지 않고, Reactor 패턴을 기반으로 하는 NIO 기술을 이용한 Netty를 사용한다.
- Java의 NIO (New I/O)
- Selector를 사용한 이벤트 기반 모델
- epoll(Linux) 또는 kqueue(macOS) 기반으로 동작
- 대표적인 예시: Netty, Java NIO 기반 서버
- Netty
- 고성능 네트워크 애플리케이션을 위한 프레임워크
- Reactor 패턴 기반의 EventLoopGroup 사용
- Spring WebFlux, gRPC, Kafka 등의 내부에서도 사용됨
정확한 이해를 위해서 다음 개념을 소개하겠다.
Reactive Stream
스트림, 즉 연속적인 흐름 형태의 데이터를 처리하는 API
스트림 형태의 데이터는 항상 Upstream에서 Downstream으로 흐른다.
upstream : 데이터를 발행하는 Publisher가 위치
downstream : 데이터를 소비하는 Subscriber가 위치
스트림 데이터 처리 개괄
1. downstream에서 subscribe()를 통한 구독 요청
2. upstream은 구독 수락을 통해 구독(Subscription) 객체를 반환
3. 반환된 구독 객체를 통해 데이터를 요청*
4. 데이터 전달
5. 데이터를 모두 전달하면 완료 시그널 전송 및 구독 삭제
백프레셔(Back-pressure)
upstream과 downstream간의 데이터 전송 속도 차이를 조절하기 위한 기술
subscriber가 데이터를 처리할 수 있는 속도가 Publisher보다 느린 경우 조절하기 위한 전략을 제공한다.
Spring Webflux (Reactor Project)
Spring Webflux는 Reactive Stream의 구현체인 Reactor Project를 기반으로 구현된 비동기 웹 애플리케이션 개발 프레임워크인 셈이다.
Spring Webflux는 Servlet 기반의 블로킹 처리 대신, Publisher로 Mono와 Flux를 이용하는 Reactor를 기반으로 하기에 비동기, Non-Blocking 방식으로 동작한다.
기본적으로 Netty를 사용해 Reactor 패턴을 따르며 select를 이용해서 단일 쓰레드에서 여러 IO들을 모니터링해서 이벤트를 감지하고 적절한 핸들러를 호출한다.
블로킹 없이 이벤트 루프 방식으로 동작해서 리소스 사용에 효율적이지만, 처리 순서가 보장되지 않아서 트랜잭션 관리에 어려움이 있다.