Netty - 네티 개념과 아키텍쳐

2020. 2. 1. 14:12Web/Netty

 

오늘 다루어볼 포스팅 내용은 Netty의 개념과 아키텍쳐에 대한 대략적인 설명이다. Netty에 대해 알아보기 전에 AS-IS 자바의 네트워킹 동작 방식에 대해 먼저 다루어본다.

 

자바의 네트워킹

순수 자바로 네트워크 통신을 하기위해서 생긴 최초의 라이브러리는 java.net 패키지이다. 해당 소켓 라이브러리가 제공하는 방식은 블로킹 함수만 지원했다. 해당 라이브러리를 이용한 서버코드를 간단히 보면 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public void blockCall() throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        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 ("OK".equals(request)) {
                break;
            }
            response = processRequest(request);
            System.out.println(response);
        }
    }
 
    public String processRequest(String request) {
        return request + "Done";
    }
cs

 

해당 코드는 한 번에 한 연결만 처리한다. 다수의 동시 클라이언트를 관리하려면 새로운 클라이언트 Socket마다 새로운 Thread를 할당해야한다. 이런식의 블로킹 처리는 어떠한 결과를 초래하게 될 것인가? 여러 스레드가 입력이나 출력 데이터가 들어오기를 기다리며 무한정 대기 상태로 유지될 수 있고, 이것은 고로 리소스의 낭비로 이어진다. 그리고 하나의 연결당 하나의 스레드가 생성되므로, 많은 수의 클라이언트를 관리하기 위해서는 많은 수의 스레드를 생성해야 하고, 이것은 리소스 낭비는 물론 잦은 컨텍스트 스위칭에 의한 오버헤드가 발생하게 된다. 

 

이러한 문제때문에 그 다음 나온 자바의 네트워킹 통신은 자바 NIO방식이다.

 

자바 NIO

해당 방식은 네트워크 리소스 사용률을 세부적으로 제어할 수 있는 논블로킹 호출이 포함되어 있다. 논블로킹은 내부적으로 시스템의 이벤트 통지 API를 이용해 논블로킹 소켓의 집합을 등록하면 읽거나 기록할 데이터가 준비됐는지 여부를 알 수 있다. 즉, 계속해서 기다릴 필요가 없어지는 것이다.

 

 

OIO와는 달리 NIO는 채널과 소켓이 1:1 매칭되지 않는다. java.nio.channels.Selector가 논블로킹 Socket의 집합에서 입출력이 가능한 항목을 지정하기 위해 이벤트 통지 API를 이용하기 때문에, 언제든지 읽기나 쓰기 작업의 완료상태를 확인가능하다. 그렇기 때문에 채널당 하나의 쓰레드가 분배되지 않고(블록킹하면 기다리지 않아도) 필요할때마다 쓰레드에게 통지를 하므로써 필요할때만 일을 할 수 있고, 필요하지 않을때는 다른 일을 할 수 있게 한다. 이런식의 처리흐름을 도입함으로써

 

  • 적은 수의 스레드로 더 많은 연결을 처리할 수 있어 메모리 관리와 컨텍스트 스위치에 대한 오버헤드가 준다.
  • 입출력을 처리하지 않을 때는 스레드를 다른 작업에 활용할 수 있다.

 

라는 장점이 생긴다. 하지만 그에 따른 단점이 존재한다면, 순수 자바 라이브러리를 직접 사용해 애플리케이션을 제작하기에는 아주 어렵다. 특히 부하가 높은 상황에서 입출력을 안정적이고 효율적으로 처리하고 호출하는 것과 같이 까다롭고 문제 발생이 높은 일은 어려운 일이기 때문이다.

 

이러한 어렵고 까다로운 일을 대신해주기 위해 네티와 같은 네트워크 프레임워크가 존재하는 것이다.

 

Netty(네티)

네티는 위와 같이 어려운 자바의 고급 API를 내부에 숨겨 놓고, 사용자에게 비즈니스 로직에만 집중할 수 있도록 추상화한 API를 제공한다. 또한 적은양의 리소스를 소모하면서 더 많은 요청을 처리할 수 있게 설계되었으므로 확장하기에도 부담이 없다. 아래는 네티의 특징을 요약한 표이다.

 

category feature
설계 단일 API로 블로킹과 논블로킹 방식의 여러 전송 유형을 지원하며, 단순하지만 강력한 스레딩 모델을 제공한다. 
이용 편의성 JDK 1/6+을 제외한 추가 의존성이 필요 없다.
성능 코어 자바 API보다 높은 처리량과 짧은 지연시간을 갖는다. 풀링과 재사용을 통한 리소스 소비를 감소시켰고, 메모리 복사를 최소화하였다.
견고성 저속, 고속 또는 과부하 연결로 인한 OOM이 잘 발생하지 않는다.(요청을 처리하는 스레드 수가 굉장히 적기 때문) 고속 네트워크 상의 NIO 애플리케이션에서 일반적인 읽기/쓰기 비율 불균형이 발생하지 않는다.
보안 완벽한 SSL/TLS 및 StarTLS 지원. 애플릿이나 OSGi 같은 제한된 환경에서도 이용 가능.

 

비동기식 이벤트 기반 네트워킹

 

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

 

위의 특징들을 종합해보면 블로킹 입출력 방식을 이용할 때보다 더 많은 이벤트를 훨씬 빠르고 경제적으로 처리할 수 있다. 

 

네티의 핵심 컴포넌트들

  • Channel
  • Callback
  • Future
  • 이벤트와 핸들러

Channel은 하나 이상의 입출력 작업을 수행할 수 있는 하드웨어 장치, 파일, 네트워크 소켓, 프로그램 컴포넌트와 같은 엔티티에 대한 열린 연결을 뜻한다. 쉽게 말해 인바운드 데이터와 아웃바운드 데이터를 위한 운송수단이라고 생각하면 좋을 것 같다.

 

Callback은 간단히 말해 다른 메서드로 자신에 대한 참조를 제공할 수 있는 메서드다. 관심 대상에게 작업 완료를 알리는 가장 일반적인 방법 중 하나이다. 네티는 이러한 콜백을 내부적으로 이벤트 처리에 사용한다.

 

Future는 작업이 완료되면 애플리케이션에게 알리는 한 방법이다. 즉, 작업이 완료되는 미래의 어떤 시점에 그 결과에 접근할 수 있게 해준다. 하지만 자바의 Future는 결과를 얻기 위해서는 반드시 블로킹해야만 한다. 그래서 네티는 비동기 작업이 실행됐을 때 이용할 수 있는 자체 구현 ChannelFuture를 제공한다. 더 자세한 내용은 뒤에서 다루어본다.

 

마지막으로 네티는 이벤트가 들어오면 해당 이벤트를 핸들러 클래스의 사용자 구현 메서드로 전달하여 이벤트를 변환하며 이러한 핸들러의 체인으로 이벤트들을 처리한다. 이벤트와 핸들러 또한 뒤에서 더 자세히 다룬다.