Netty 1

 

Spring reactive stack 에서 사용되는..

Spring webflux 는 reactor 와 netty 를 의존한다.

뿐만 아니라, Spring data redis 도 netty 를 의존한다.

 

 

이번 포스팅에서는 netty 를 알아보겠다.

 

Netty 소개

Netty 공식 홈페이지에 따르면

"Netty is asynchronous event-driven network application framework" 라 소개한다. (Server & Client)

 

Netty 는..

HTTP, SMTP, FTP 등의 protocol 뿐만 아니라 다양한 protocol 을 지원한다.

또한, 불필요한 memory copy 를 최소화 하며

Java IO, NIO, selector 기반으로 적은 리소스로 높은 성능을 보장한다.

(Reactor pattern 을 사용함)

유연하고 확장가능한 Event model 을 기반으로 관심사를 명확하게 분리할 수 있다.

 

정리

Java NIO + Selector + (epoll) + Reactor pattern 을 사용하며

I/O 관점에서 busy wait 을 I/O multiplexing 로 어느정도 극복하고, 동기, non-blocking 성격이 있다.

Netty application 내부 관점에서는 이벤트 기반 비동기로 처리를 한다.

 

이를 통해 C10K problem 을 효과적으로 대응할 수 있다고 생각한다.

 

 

Netty 주요 구성

NioEventLoop

일반적으로 단일 스레드가 연속적인 루프를 돌며.. 

I/O 네트워크 이벤트를 감지하고 처리하는 역할을 맡고있다.

 

대표적으로 아래 개념을 가진다.

 

EventExecutor

이벤트(Task) 처리를 위한 스레드풀이며, 실행 상태를 확인하는 메서드를 제공한다.

NioEventLoop 는 SingleThreadEventExecutor 를 상속한다.

즉, single thread 로 task 처리를 하며 무한 루프를 돈다.

 

TaskQueue

발생되는 Task 가 저장이되면 EventLoop 가 하나씩 꺼내서 작업하는 구조를 위해 사용되는 Queue 이다.

Task 는 I/O 이벤트(I/O task)가 될 수 도 있고 일반적인 작업(non - I/O task)일 수 도 있다.

Task 는 비동기로 작업되어야 한다.

저장되는 Task 는 Thread-safe 해야한다.

 

Selector

I/O Multiplexing 을 지원한다.

 

NioEventLoopGroup

Netty 의 EventLoopGroup 는 여러 개의 EventLoop 가 속하는 구조로 지원한다.

"new NioEventLoopGroup()" 으로 생성할 경우 내부 NioEventLoop(EventExecutor) 는

실행시킨 컴퓨터의 CPU core 개수 * 2 개가 된다.

 

EventLoop 와 EventExecutor, EventLoopGroup 은 따로 있는 개념이 아니라 하나로 보면 편하다.

NioEventLoop 는 사용자가 직접 생성할 수 없다.

 

사용자는 NioEventLoopGroup 을 생성하여 사용할 수 있다.

 

 

참고

즉, reactive stack 에서는 I/O 처리를 위해 사용되는 스레드는 고정적임을 알 수 있다.

그러나, servlet stack 에서도 I/O 처리를 위해 사용되는 스레드가 보통 thread pool 로 고정적으로 관리 된다.

하지만, servlet stack 에서는 요청 당 할당으로 요청에서 blocking 이 발생하면 그 thread 는 아무 것도 못하지만..

reactive stack 에서는 blocking 이 발생하지 않도록 하여, thread 자원을 효율적으로 사용할 수 있어서

C10K problem 을 극복할 수 있다.

 

 

Task(Event)

I/O task 와 non - I/O task 로 구분된다.

 

I/O task

NioEventLoop 가 관리하는 selector 에 등록된 channel 에서 I/O (준비완료) 이벤트가 발생하면

channel 의 pipline 을 실행한다.

 

non - I/O task

TaskQueue 에 존재하는 I/O task 가 아닌 모든 task 이다.

 

 

ioRatio

EventLoop 가 taskQueue 에서 I/O task 와 non - I/O task 를 꺼내서 수행할 때,

실행 시간의 비율이다. (기본 값은 50, 1:1 이다.)

50 이상이면, I/O task 실행시간 비율이 높은 것이다.

 

 

NioServerSocketChannel, NioSocketChannel

netty 의 Channel 은 Java nio 의 Channel 과 무관하다.

EventLoop 에 여러 개의 netty 의 Channel 을 등록할 수 있다. (register 메서드)

 

 

 

NioServerSocketChannel, NioSocketChannel 은..

각각 Java NIO 의 ServerSocketChannel, SocketChannel 과 기본적으로는 비슷한 역할을 한다.

 

 

중간에 보이는.. ServerSocketChannel, SocketChannel, Channel 은 Java nio 소속이 아님을 다시한번 상기하자..

 

EventLoop 에 Channel 을 register 메서드를 통해 등록

EventLoop 를 시작하게 만들고

EventLoop 가 selector 를 통해 Channel 에서 발생되는 I/O 이벤트를 감지하기 시작한다.

 

즉, 예를 들면..

NioServerSocketChannel 의 경우 accept 가 되면(accept 체결 완료) EventLoop 는 이를 감지할 수 있다.

 

 

ChannelPipline

EventLoop 는 Channel 에서 발생되는 I/O 이벤트를 감지하다가 발생하면 

해당 Channel 이 가지고 있는 ChannelPipline 을 실행한다.

즉, ChannelPipline 은 I/O task 에 해당하는 것이다.

 

참고

AbstractChannel 이 ChannelPipline 을 가지고 있다.

EventLoop 에 등록할 Channel 에 ChannelPipline 을 등록하는 방식으로 사용한다.

 

 

ChannelPipline 은 위 그림과 같이 ChannelInboundHandler 와 ChannelOutbound Handler 의 집합으로 이루어져있다.

 

inbound 는 보통 Channel 에서 발생된 read 이벤트를 처리하는 ChannelHandler 로 사용되며,

outbound 는 보통 Channel 에 write 를 하기 위한 ChannelHandler 로 사용된다.

 

EventLoop 가 selector 를 통해 이벤트를 감지하면 해당 Channel 에 등록되어있는

ChannelInboundHandler 를 실행하는데 ChannelInboundHandler 는 여러 개의 체인 형식으로 되어있다.

등록 순서에 따라 N 개를 순차적으로 수행한다.

 

 

위 그림은 ChannelInboundHandler 인터페이스의 일부이다.

원하는 이벤트를 받아 처리하려면 해당 이벤트에 맞는 메서드를 구현하고

ChannelInboundHandler 를 Channel 에 등록하면 ChannelPipline 으로써 동작된다.

 

참고

각 메서드의 Object 타입의 파라미터는 이벤트 결과에 대한 객체가 들어온다고 생각하면 된다.

NioServerSocketChannel 에서 accept 가 되면 channelRead 가 수행되는데

이때 전달되는 Object 는 accept 결과인 NioSocketChannel 이다.

또는, 이전 ChannelHandler 가 존재했다면 그 ChannelHandler 가 넘긴 특정 데이터일 수 있다.

 

참고

Inbound, outbound 외에 duplex 라는 핸들러가 있는데

inbound 방향(next)과 outbound 방향(prev) 모두 수행되는 핸들러이다.

 

 

 

ChannelHandlerContext

ChannelHandler 의 메서드 파라미터로 전달되는

ChannelHandlerContext 는 다음 ChannelHandler 로 넘기는 메서드를 제공하며

(Spring MVC filter 에서 doFilter 를 연상해보자)

inbound 핸들러에서 outbound 핸들러로 방향 전환할 때도 사용된다.

Channel 로 write 할 데이터를 보낼 수도 있다.

 

ChannelHandler 는 ChannelHandlerContext 에 의해 관리된다.

또한, ChannelHandlerContext 에는 이전, 이후 의 ChannelHandlerContext 를 가지고 있다.

그래서, ChannelHandlerContext 로 다음에 수행되어야 하는 ChannelHandler 로 넘길 수 있는 것이다.

 

ChannelHandler, ChannelHandlerContext, ChannelPipline, Netty 의 Channel 의 관계는

위 그림을 참조하면 쉽다.

 

ChannelFuture

위는 netty 의 ChannelFuture 설명과 코드 일부이다.

 

netty 내부적으로 사용하는 Future 이다.

Java Future 를 상속받아 자체적으로 ChannelFuture 라는 객체를 사용한다.

netty 의 Channel I/O 작업이 완료되면 isDone() 을 호출 했을 때, true 를 반환한다.

addListeners / removeListeners 등을 통해 FutureListener 를 등록하거나 삭제할 수 있다.

 

이를 통해 EventLoop 는 내부 selector 를 통해 I/O 작업이 완료됨을 감지하면

등록된 FutureListener 를 수행될 것임을 추론해볼 수 있다.

사용자는 리스너를 통해 비동기로 특정 작업을 수행할 수 있는 것이다.

 

참고

Netty 의 ChannelHandlerContext 는 Channel{Inbound/outbound}Invoker 를 상속하는데..

ChannelXXXInvoker 가 제공하는 대부분의 메서드 리턴 타입이 ChannelFuture 이다.

따라서, ChannelHandlerContext 로 리스너를 등록해볼 수 도 있다.

 

 

 

 

 

 

참고하면 좋음

https://aws.plainenglish.io/netty-core-component-eventloop-source-code-analysis-cb7e53adc8e5

 

 

 

 

'Spring Reactive Stack > Netty' 카테고리의 다른 글

Netty 2  (0) 2024.01.21
Proactor pattern  (0) 2023.11.20
Reactor pattern  (0) 2023.11.19