Netty 리액터 패턴의 동작 원리 소스 코드 분석

1. EventLoop의 개념

EventLoop은 간단히 설명하면 이벤트를 지속적으로 감시하는 무한 루프 구조입니다. 이벤트가 발생하면 해당 이벤트를 처리하는 역할을 담당합니다. 일반적인 구현 방식은 별도의 스레드를 생성하여 끊임없이 루프를 실행하는 것입니다.

기본 구조:

while (selector.select()) {
    processSelectedKeys();
}

2. EventLoopGroup의 구조

EventLoopGroup은 복수의 EventLoop을 관리하는 그룹입니다. 여러 개의 EventLoop을pooling하여 병렬 처리能力和 高 성능을 구현할 수 있습니다.

3. bossGroup과 workGroup의 역할 분배

Netty의 서버 구현에서 bossGroup과 workGroup은 다음과 같이 역할을 분리합니다:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

bossGroup의 역할

bossGroup은 NioServerSocketChannel을 관리하며 클라이언트의 연결 요청을 처리합니다. 단일 포트를 사용하는 서버의 경우 bossGroup은 하나의 EventLoop만으로도 충분합니다. 여러 ServerBootstrap이 bossGroup을 공유하지 않는 한, 불필요하게 많은 EventLoop을 할당하는 것은 자원 낭비입니다.

서버 소켓 채널이 생성되어 특정 포트에 바인딩되면, 클라이언트 연결이 들어올 때마다 accept() 메서드가 새로운 SocketChannel을 생성합니다. 모든 SocketChannel의 입출력 작업은 workGroup에서 처리되어 성능을 최적화합니다.

workGroup의 역할

workGroup은 실제 데이터 읽기/쓰기 작업을 담당합니다. 연결된 클라이언트마다 개별 SocketChannel이 생성되며, 이 채널들의 모든 IO 작업은 workGroup 내의 EventLoop에서 처리됩니다.

4. 소스 코드 레벨 분석

서버 채널 등록 과정

포트 바인딩부터 채널 등록까지의 과정은 다음과 같습니다:

1단계: bind(port).sync() 호출

2단계: AbstractBootstrap.doBind()에서 실제 바인딩 수행

3단계: AbstractBootstrap.initAndRegister()에서 config().group().register(channel) 호출

4단계: MultithreadEventLoopGroup.register()에서 next().register(channel) 호출

여기서 next() 메서드는 Chooser를 통해 Group에서 하나의 EventLoop을 선택합니다.

등록 메커니즘 핵심 코드

if (currentThread == eventLoopThread) {
    register0(channel);
} else {
    eventLoop.execute(() -> register0(channel));
}

현재 스레드가 이미 EventLoop 스레드라면 직접 register0()을 실행하고, 그렇지 않으면 이벤트 루프에 작업을 제출합니다. 이것이 바로 리액터 패턴의 핵심입니다.

이벤트 루프의 두 가지 역할

NioEventLoop은 항상 두 가지 작업을 수행합니다:

  1. IO 이벤트 처리: Selector에서 발생하는 네트워크 이벤트를 처리
  2. 태스크 실행: 다른 스레드에서 제출된 작업들을 순차적으로 실행

5. workGroup의 동작 방식

workGroup의 동작 방식은 bossGroup과 기본적으로 동일합니다. 유일한 차이점은 workGroup은 각 SocketChannel마다 개별 EventLoop을 할당한다는 점입니다.

ServerBootstrapAcceptor의 핵심 로직:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    childGroup.register(child);
}

childGroup.register()는 앞서 설명한 bossGroup의 등록 과정과 동일합니다. 클라이언트 연결마다 workGroup에서 EventLoop을 선택하여 할당하고, 해당 EventLoop이 이미 실행 중이라면 새 작업을 제출합니다. 하나의 EventLoop이 여러 클라이언트 채널을 담당할 수 있습니다.

결론

이렇게Netty는 bossGroup이 클라이언트 연결을 담당하고, workGroup이 데이터 입출력을 담당하는 구조로 설계되어 있습니다. 하나의 ServerBootstrap은 하나의 boss EventLoop을 사용하고, 성능을 위해 workGroup에는 적절数量的 EventLoop을 구성하는 것이 중요합니다.

태그: Netty reactor-pattern NIO eventloop java

6월 7일 01:14에 게시됨