IT

Netty의 개념과 아키텍처

코딩하는 너구리 2020. 12. 9. 10:18
반응형

Netty

: 비동기식 이벤트 기반 네트워킹 프레임워크

 

 

일반적인 JAVA 블로킹 입출력 예제

ServerSocket serverSocket = new ServerSocket(portNumber);
Socket clientSocket = serverSocket.accept();

BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

String request, response;

while((request = in.readLine)) != null) {
	if ("Done".equals(request))
    	break;
	response = processRequest(request);
    out.println(response);
}

 

간단한 예제를 하나 살펴보자. 이 예제에서 accept()는 ServerSocket에서 연결될 때까지 진행을 블로킹하며 연결되면 클라이언트와 서버간 통신을 위한 새로운 Socket을 하나 반환한다.

또한 이 코드는 한 번에 한 연결만 처리한다.

 

기본적인 Socket API의 문제점

위 방식을 살펴보면, 여러 스레드가 입력이나 출력 데이터가 들어오기를 기다리며 무한정 대기 상태에 빠질 수 있다.

- 무한 대기(좀비), 리소스 낭비, 오버헤드 등등..

 

 

자바 NIO

위와 같은 문제를 갖는 블로킹 시스템 호출 방식 외에도 네이티브 소켓 라이브러리에는 오래전부터 네트워크 리소스 사용률을 세부적으로 제어할 수 있는 논블로킹 호출이 포함돼 있었다.

* setsockopt()를 이용하면 데이터가 없을 때, 즉 블로킹 호출이라면 진행을 블로킹할 상황에서 읽기/쓰기 호출이 즉시 반환하도록 소켓을 구성할 수 있다.

논블로킹 입출력을 위한 자바 지원 기능은 2002년 JDK 1.4 패키지인 java.nio와 함께 도입됐다.
NIO는 원래 NEW input/output의 약자였지만, 대부분의 사용자는 NIO가 논블로킹 입출력을 나타내며, 블로킹 입출력은 OIO라고 생각한다.

 

 

 

Selector

- 아래 그림의 논블로킹 설계를 보면 이전에 설명한 블로킹 방식의 단점이 사실상 완전히 해결된 것을 알 수 있다.

- 자바의 논블로킹 입출력 구현의 핵심으로서, 논블로킹 Socket의 집합에서 입출력이 가능한 항목을 지정하기 위해 이벤트 통지 API를 이용한다.

 

 

언제든지 읽기나 쓰기 작업의 완료 상태를 확인할 수 있으므로 한 스레드로 여러 동시 연결을 처리할 수 있다.

  • 적은 수의 스레드로 더 많은 연결을 처리할 수 있으므로 메모리 관리와 컨텍스트 전환에 따르는 오버헤드가 감소한다.
  • 입출력을 처리하지 않을 때는 스레드를 다른 작업에 활용할 수 있다.

 

 

비동기성과 확장성은 어떻게 연결돼 있을까?

먼저 비동기의 개념에 대해 생각해보면 좋을 것 같다. 비동기의 좋은 예시로 이메일이 있다.

보낸 메시지의 답장이 올 수도 있고 답장이 없을 수도 있다. 메시지를 보내는 동안 예기치 않는 메시지를 받을 수도 있다. 

또한 비동기 이벤트는 정돈된 관계를 가질 수 있다는 것을 알아두자.

일반적으로 답변은 질문한 사항에 대해서만 받을 수 있고, 답변을 기다리는 동안 다른 일을 할 수도 있다.

 

비동기성과 확장성은 어떻게 연결돼 있을까?

  • 논블로킹 네트워크 연결은 작업 완료를 기다릴 필요가 없게 해준다. 완전 비동기 입출력은 이 특징을 바탕으로 한 단계 더 나아간다. 비동기 메서드는 즉시 반환하며 작업이 완료되면 직접 또는 나중에 이를 통지한다.
  • 셀렉터는 적은 수의 스레드로 여러 연결에서 이벤트를 모니터링할 수 있게 해준다.

논블로킹 입출력을 이용하면 블로킹 입출력 방식을 이용할 때보다 더 많은 이벤트를 훨씬 빠르고 경제적으로 처리할 수 있다. 이것은 네트워킹 관점에서 우리가 구축하려는 시스템의 핵심이며, 앞으로 알아보겠지만 네티 설계의 핵심이기도 하다.

 

 

 

네티의 핵심 컴포넌트

 

Channel

하나 이상의 입출력 작업(읽기 또는 쓰기)을 수행할 수 있는 하드웨어 장치, 파일, 네트워크 소켓, 프로그램 컴포넌트와 같은 엔티티에 대한 열린 연결

일단 Channel을 들어오는 데이터와 나가는 데이터를 위한 운송수단이라고 생각하자. Channel은 열거나 닫고, 연결하거나 연결을 끊을 수 있다.

 

콜백

콜백은 간단히 말해 다른 메서드로 자신에 대한 참조를 제공할 수 있는 메서드다.
콜백은 관심 대상에게 작업 완료를 알리는 가장 일반적인 방법 중 하나다.

네티는 이벤트를 처리할 때 내부적으로 콜백을 이용한다. 콜백이 트리거되면 ChannelHandler 인터페이스의 구현을 통해 이벤트를 처리할 수 있다.

 

Future

Future는 작업이 완료되면 이를 애플리케이션에 알리는 한 방법이다. 이 객체는 비동기 작업의 결과를 담는 자리표시자 역할을 하며, 미래의 어떤 시점에 작업이 완료되면 그 결과에 접근할 수 있게 해준다.

JDK는 Future를 제공하지만, 수동으로 작업완료 여부를 확인하거나 완료되기 전까지 블로킹하는 기능만 있다. 따라서 네티는 비동기 작업이 실행됐을 때 이용할 수 있는 자체 구현 ChannelFuture를 제공한다.

네티의 모든 아웃바운드 입출력 작업ChannelFuture를 반환하며 진행을 블로킹하는 작업은 없다.

앞서 언급한 대로 네티는 기본적으로 비동기식이며 이벤트 기반이다.

 

ChannelFutureListener를 활용하는 방법을 살펴보자.

먼저 원격 피어로 연결한 다음, connect() 호출로 반환된 ChannelFuture를 이용해 새로운 ChannelFutureListener를 등록한다. 리스너가 연결이 만들어졌다는 알림을 받으면 상태를 확인한다. 작업이 정상적이면 데이터를 Chanel로 기록하며, 그렇지 않으면 ChannelFuture에서 Throwable을 가져온다.

 

Channel channel = ...;
// 블로킹하지 않음
ChannelFuture future = channel.connect(new InetSocketAddress("192.168.0.1", 25));

future.addListener(new ChannelFutureListener() {
	@Override
    public void operationComplete(ChannelFuture future){
    	if(future.isSuccess()){
        	ByteBuf buffer = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
            ChannelFuture wf = future.channel().writeAndFlush(buffer);
            ...
        } else {
        	Throwable cause = future.cause();
            cause.printStackTrace();
        }
    }
}

 

 

 

이벤트와 핸들러

네티는 작업의 상태 변화를 알리기 위해 고유한 이벤트를 이용하며, 발생한 이벤트를 기준으로 적절한 동작을 트리거할 수 있다. 다음과 같은 동작이 포함된다.

  • 로깅
  • 데이터 변환
  • 흐름 제어
  • 애플리케이션 논리

 

네티는 네트워크 프레임워크이므로 이벤트 역시 인바운드 또는 아웃바운드 데이터 흐름에 대한 연관성을 기준으로 분류된다.

 

인바운드 데이터나 연관된 상태 변화로 트리거되는 이벤트는 다음을 포함한다.

  • 연결 활성화 또는 비활성화
  • 데이터 읽기
  • 사용자 이벤트
  • 오류 이벤트

 

아웃바운드 이벤트는 다음과 같이 미래에 한 동작을 트리거하는 작업의 결과들이다.

  • 원격 피어로 연결 열기 또는 닫기
  • 소켓으로 데이터 쓰기 또는 플러시

 

ChannelHandler의 체인을 통한 인바운드와 아웃바운드 이벤트의 흐름

 

 

정리

네티의 비동기 프로그래밍 모델은 Future, 콜백의 개념, 그리고 더 깊은 단계에서 이벤트를 핸들러 메서드로 발송하는 작업을 기반으로 작동한다.

작업을 가로채고 인바운드나 아웃바운드 데이터를 즉시 변환하려면 콜백을 제공하거나 작업이 반환하는 Future를 활용하면 간단하다.

 

또한 셀렉터, 이벤트, 이벤트 루프에 대해 알아두자.

네티는 이벤트를 발생시켜 Selector를 애플리케이션 밖으로 추상화하므로 개발자가 발송 코드를 직접 작성할 필요가 없다. 각 Channel에 해당하는 EventLoop는 내부적으로 모든 이벤트를 처리한다.

- 관심 이벤트 등록

- 이벤트를 ChannelHandler로 발송

- 추가 동작 스케쥴링

EventLoop 자체는 하나의 Channel의 모든 입출력 이벤트를 처리하는 스레드에 의해 제어되며, EventLoop의 수명 기간 동안 달라지지 않는다.

 

 

 

 

두 유저가 전화를 한다고 가정했을 때, 두 사람 사이에 생긴 전화선을 Channel(채널)로 본다면

왼쪽 유저의 입장에서 오른쪽으로 나가는 이벤트를 아웃바운드 이벤트,

반대로 상대로부터 들어오는 이벤트를 인바운드 이벤트로 볼 수 있다.

또 전화 연결, 전화 끊어짐, 통화중 등 미래에 발생할 수 있는 시점들이며 이를 Future로 볼 수 있고

이러한 이벤트를 알리는 것은 Callback을 통해 이루어진다.

 

 

 

 

참고 https://book.naver.com/bookdb/book_detail.nhn?bid=10462610

 

네티 인 액션

네티는 복잡한 네트워킹, 멀티스레드, 동시성을 관리하는 자바 기반 네트워킹 프레임워크로서, 반복적인 저수준 코드를 내부로 감춤으로써 비즈니스 논리를 분리하고 쉽게 재사용할 수 있게 해

book.naver.com

 

반응형