이 글을 읽기 전에 Reactor 모델 등장 배경을 설명하는 https://ghkdqhrbals.github.io/portfolios/docs/Java/5 를 보고 오시면 이해에 도움이 될 것입니다!
본 포스팅은 아래의 Reference들을 번역하고 재정리한 글입니다.
Reference
- https://www.alibabacloud.com/blog/essential-technologies-for-java-developers-io-and-netty_597367
- https://medium.com/geekculture/a-tour-of-netty-5020ecee5494
1. Netty 란?
1.1 Netty Architecture
네티는 JVM 위에서 돌아가는 Reactor I/O 프레임워크입니다.
이전 Reactor Model 에서 설명한 Master-slave Reactor 모델과 비슷하지만 조금 다른데요. 이제부터 알아보겠습니다.
보시면 크게 Boss Group, Worker Group, Executor Group 이 세 가지 그룹으로 나뉘는 것이 보이죠?
Master-slave Reactor 모델과 매칭시켜보면
- Boss Group->Event Loop = Main Reactor
- Worker Group->Event Loop = Sub Reactor
- Executor Group = Thread Pool
위와 같이 매칭됩니다. 조금은 다르겠지만요.
순서를 볼까요?
1.2 Netty 동작 순서
- 클라이언트 요청
- Boss Group 은 클라이언트와 connection 요청을 establish 및 NioSocketChannel 를 만듭니다.
- 그리고 Boss Group 은 자신의 Event Loop 를 돌리면서 모든 NioSocketChannel 를 Worker Group 의 Nio Event Group 이 관리하는 selector에 등록시키죠.
- Worker Group 의 Event Loop 는 계속 돌고 있습니다. 얘는 자신의 selector 에 연결된 채널들에 이벤트를 계속 Push 해주는 역할을 수행하죠. 즉, 자신의 큐에 저장된 네트워크 Inbound 이벤트를 빼서 NioSocketChannel 에 Push 하게 됩니다.
- NioSocketChannel 에서 이벤트를 수신하면, ChannelPipeline 에 들어갑니다.
- ChannelPipeline 은 여러개의 ChannelHandler가 ChannelContext에 의해 linking 되어있습니다. Inbound 이벤트는 소켓을 읽어주는
ChannelInboundHandler
가 얘를 인터셉트해서 Task를 수행하게 됩니다. - 이 때, Executor Group의 Thread Pool 에서 스레드를 빼와서 Task를 수행하게 됩니다.
- 이후, 클라이언트에게 요청을 반환하기 위한
ChannelOutboundHandler
가 호출되고 소켓 Write를 하게 됩니다.
종합하면 Boss Group 은 Worker Group 내 여러 이벤트 루프들에게 계속해서 이벤트를 쏴주고, 이벤트 루프는 하나의 selector을 이용해서 여러 채널과 여러 핸들러를 운영합니다. 그리고 핸들러들은 read/write 만 담당하며 실제 로직은 Executor Group의 Thread Pool 과 연결되어 수행되게 됩니다. Master-slave Reactor 모델과 매우 유사하죠?
1.3 Netty 장점
이를 통해 만약 특정 클라이언트 요청에 사용되는 비즈니스 로직이 오래걸려도, 다른 요청들은 blocking 되지 않죠! 왜냐하면 Handler는 단순히 read/write 만을 담당하기 때문이죠. 즉, 1. I/O 와 비즈니스 로직을 분리시킨 모델입니다. 또한 2. I/O 가 시간이 오래 걸려도, 다른 Channel 에게 영향을 주지 않게 되죠. 예로 네트워크 Read/Write 와 파일 Read/Write 는 서로 분리된 채널(채널 별 Thread 할당) 이기 때문에, 파일 I/O가 시간이 오래 걸려도 네트워크 I/O에는 영향을 주지 않죠.
다시한번 종합하면, Netty는 비동기 non-blocking에 최적화된 모델이라고 볼 수 있겠습니다!
1.4 Netty Component Details
1.4.1 Channel
네티의 채널은 NIO 의 채널과 동일하게 read/write을 수행하는 컴포넌트입니다. ChannelPipeline
는 여러개의 ChannelHandler
로 이루어져 있으며, ChannelHandler
는 양방향 linked list 로 연결되어 있습니다. Inbound/Outbound 이벤트들은 ChannelPipeline
를 거쳐서 맞는 ChannelHandler
로 흐르게 되죠. 하나의 Channel
은 하나의 ChannelPipeline
과 연결되어 있습니다. ChannelHandler
은 이벤트를 Read/Write 하는 역할을 수행합니다. 그리고 실제 비즈니스 로직들은 Executor Group 의 스레드풀에서 스레드 하나 가져와 처리됩니다.
Channel is a component providing users a ways to process I/O operations, such as read and write. A ChannelPipeline encapsulates a series of ChannelHandler instances as two-way linked list. Inbound and outbound events that flow through a channel can be intercepted by the ChannelPipeline. Whenever a channel is created, a ChannelPipeline is created and permanently bound to the channel. Triggered events can be intercepted, passed, ignored or terminated by ChannelHandler. The head of the linked list is HeadContext, and the tail of the linked list is TailContext.
1.4.2 ChannelHandlers
얘도 마찬가지로 NIO 의 Handler와 동일해요. 조금 다른점은 PipeLine 내 여러개가 ChannelHandlerContext 에 의해 양방향 linking 된다는 점이죠.
ChannelHandlers Handles or intercepts events and forwards it to the next handler in a ChannelPipeline. Based on its origin, an event is handled by the ChannelInboundHandler or ChannelOutboundHandler, and the ChannelHandlerContext forwards the event to the next ChannelHandler.
1.4.3 Netty NioEventLoopGroup
얘는 하나의 NIO selector, 하나의 큐, 하나의 스레드로 동작합니다. 이벤트들을 큐에 넣고 관리를 하면서 채널에 계속 엮어주는 역할을 수행하죠.
NioEventLoopGroup contains multiple NioEventLoops and manages their lifecycles. Each NioEventLoop contains an NIO selector, a queue, and a thread. The thread is used to poll the read and write events of the channels registered to the selector and handle the events that are delivered to the queue.