728x90
반응형

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Netty

Netty는 서버-클라이언트 간 유지보수와 규모조정이 용이한 프로토콜(또는 최적화된 프로토콜)의 신속한 개발을 목적으로 개발된 이벤트-드리븐 방식의 네트워크 애플리케이션 프레임워크이다. 한국인 개발자 이희승씨가 만든 프레임워크로도 유명하다. 사용자(개발자)는 Netty가 제공하는 틀에서 일반적인 메시지 포맷을 활용하거나 Binary ↔ Structured Data를 변환해주는 protobuf, thrift 등과 같은 라이브러리와 결합해 구현하면 된다. 타 프레임워크에 붙이기 용이하고 사용층이 두텁기 때문에 protobuf나 thrift 같은 직렬화 프로토콜에 대한 인코더/디코더도 직접 구현할 필요 없이 찾아보면 있다.

Netty는 프레임워크 차원에서 기본적인 Thread-safety를 보장하므로 사용자는 비교적 쉽게 멀티 스레드로 동작하는 비동기 네트워크 애플리케이션을 만들 수 있다. Netty의 3.x 버전은 deprecated된 상태(maintenance는 되고 있음.)이고, 4.0이 현재의 안정화 버전, 4.1은 4.0에 대한 하위호환성을 제공하면서 HTTP/2 같은 기능을 추가로 제공한다.

*Hello Netty

String sendMessage = "Hello, Netty!"; ByteBuf messageBuffer = Unpooled.buffer(); messageBuffer.writeBytes(sendMessage.getBytes()); ctx.writeAndFlush(messageBuffer);

Netty가 쓰이는 곳.

아래 링크에 들어가보면 Java 기반 굵직한 프레임워크에서 심심치 않게 쓰이는 것을 확인할 수 있다.

gRPC, Vert.x, Armeria, ...

(https://netty.io/wiki/adopters.html)

참고 사이트

여기에 Netty에 대해 구구절절히 적는다고 해도 아무도 읽을 것 같지 않다. :-(

프레임워크에서 커버해주는 기능이 아무리 편리한들. 기본적인 블로킹/넌블로킹의 동작, 소켓에 대해 내용을 다시 한번 훑고나서 공부하는 것이 도움이 된다.

가이드 문서는 아래 내용을 살펴보면 되고, 감사하게도 번역 글이 있다.

Netty User Guide : https://netty.io/wiki/user-guide-for-4.x.html

Netty User Guide 번역 : http://rahs.tistory.com/200 // http://ikpil.com/1338

구글에 Netty Korean User Group도 있다.

https://groups.google.com/forum/#!forum/netty-ko

종이로된 책이 편하면

'자바 네트워크 소녀 네티'라는 서적이 출간되어 있다. 읽어본 바 표지가 오그라드는 것 빼고 내용은 알차다. (네티 인 액션은 읽어보지 않음.)

+ 주 컨셉

1) Channel: 읽기, 쓰기, 연결 등의 I/O 작업이 가능한 통로. 주로 NioServerSocketChannel, NioSocketChannel을 사용하게 되어 있다.

2) Pipeline: 단방향 큐의 구조로 인바운드, 아웃바운드, 인아웃바운드 핸들러가 연결되어 있다. 각 핸들러는 서블릿 필터와 유사한 역할을 수행한다. (Intercepting Filter Pattern)

3) Handler: 이벤트 핸들러는 네티 추상화 계층에서 애플리케이션 개발자와 가장 밀접하게 연관된 부분이다. 인바운드, 아웃바운드, 그리고 인아웃바운드 핸들러로 구분된다. 인바운드 핸들러(Decoder)는 읽기 I/O 이벤트를 처리하는데 ByteBuf를 데이터 스트럭쳐로 변환하는 역할을 수행한다. 아웃바운드 핸들러(Encoder)는 쓰기 I/O 이벤트를 처리하는데 데이터 스트럭쳐를 ByteBuf로 변환하는 역할을 수행한다. 인아웃바운드 핸들러는 I/O 작업 외 주로 비즈니스 로직에 대한 것이다.

* 필요한 핸들러를 직접 구현하기 전에 기본적으로 제공되는 핸들러 목록을 확인할 것. IdleStateHandler, ReadTimeoutHandler, WriteTimeoutHandler, StringDecoder/Encoder, ...

4) ChannelOption: 일반적인 소켓 옵션에 대한 것을 지정할 수 있다.

- TCP_NODELAY: Nagle 알고리즘 비활성화 여부를 설정한다. (기본값: false)

- SO_KEEPALIVE: 운영체제에 지정된 시간에 한번씩 keepalive packet을 전송해서 서버-클라이언트간 소켓 통신이 가능한지 파악한다. 해당 패킷의 발송 주기는 커널 변수로 관리되므로 변경시 다른 모든 소켓에도 동일하게 적용된다는 점에 주의하여야 한다. (기본값: false)

- SO_REUSEADDR: TIME_WAIT 상태의 포트에 대해서도 bind 가능해진다. (응용 프로그램에서 소켓 사용 중단을 하더라도 커널에서 일정 시간 동안 사용하던 포트를 잡고있는 경우가 있다.) 또는 두 개 이상의 IP 주소를 갖는 호스트에서 IP 주소별로 서버를 운용할 경우에도 이 옵션을 사용하면 사용 중인 포트에 대해서 소켓을 주소에 연결(bind)할 수 있다. (기본값: false)

- SO_LINGER: 이 옵션을 사용하여 응용 프로그램이 Close 함수를 호출할 때 커널에서 응용 프로그램으로 복귀하는 시점을 송신 버퍼의 자료가 모두 전송된 것이 확인될 때까지 지연할 수 있다. 포트 상태가 TIME_WAIT 상태로 전환되는 것을 방지하기 위해 SO_LINGER 옵션을 활성화하고 타임아웃 값을 0으로 설정하기도 한다. (기본값: false)

- SO_BACKLOG: 동시에 수용 가능한 소켓 연결 요청 수. 동시 connect 요청에 대한 것이므로 최대 허용 동접자 수와는 무관한 개념이다.

+ 기억할 것들

1) Netty의 자료구조인 ByteBuf는 flip()을 필요로 하지 않는다. 읽기용 쓰기용 포인터를 각각 가지고 있다. ByteBuf의 성능도 무지 좋다. (Zero-copy ByteBuf)

2) Netty의 모든 동작은 비동기적이다. 작업의 결과로 Future 객체를 반환한다. ChannelFuture? 네티에서 비동기 메서드를 호출하면 ChannelFuture 객체를 돌려준다. 이 객체를 통해서 비동기 메서드의 처리 결과를 확인할 수 있는데 ChannelFuture의 sync 메서드는 ChannelFuture 객체의 요청이 완료될 때까지 대기하게 한다.

* Future?

// 오랜 시간이 소요되는 작업을 호출(비동기) 후 필요한 시점에 결과를 요청하는데, // 작업이 끝나지 않은 상태라면 get()에서 끝날때까지 대기한다. Future<BigData> data = loadBigData(); // expensive call, async. doSomething(); handleData(data.get());

3) ChannelHandler의 메소드는 동시에 호출되지 않는다. 그리고 Channel은 하나의 스레드에 할당되며, 그 스레드에서만 호출된다. (물론 하나의 스레드가 여러 개의 Channel을 처리할 수 있다.) Channel의 입장에선 어지간해선 Single Thread 모델로 봐도 되는 셈.

4) 핸들러에게 전달된 참조형 객체(ByteBuf)의 해제는 핸들러의 책임이다. 미리 제공된 디코더를 사용할 경우엔 해당 디코더에서 정상적인 release 작업이 이루어지는지 확인하자. (ReferenceCountUtil.release(msg). write의 경우, write가 완료된 이후에 Netty가 해당 객체에 대한 해제를 수행해준다.)

5) 파편화 문제를 예방하는 코드를 사용할 것. Decoder(in.readableBytes().)

6) @Sharable; 이 어노테이션을 쓰면 핸들러 인스턴스는 하나만 생성되며, 여러 채널의 파이프라인에 추가되어 공유될 수 있다. 리소스가 절약된다. 단, 핸들러에 race condition이 있으면 안된다.

@ 동기/비동기, 블로킹, 논블로킹 간략 정리.

동기와 비동기는 일종의 서비스 호출에 대한 개념, 블로킹과 논블로킹은 소켓 동작 방식에 대한 개념으로 우선 구분하자.

1) 동기: 일관된 흐름. 상호간 싱크(타이밍)을 맞추는 것. 호출의 결과를 확인할 때까지 대기해야 한다.

2) 비동기: 일관되지 않은 흐름. 호출 이후 호출 결과를 받기 전 사이 시간에 다른 작업을 할 수 있다.

///

3) 블로킹: 요청 작업이 성공하거나 에러가 발생하기 전까지 응답을 돌려주지 않는다. 블로킹 소켓에서의 병목 지점은 accept로, accept 메서드는 특정 시간에 하나의 연결만을 처리할 수 있기 때문에 클라이언트가 동시에 접속 요청을 할 경우 대기 시간이 길어지게 된다. 블로킹 소켓에선 하나의 클라이언트 소켓을 처리하기 위해 별도의 스레드를 할당하는 방식이 일반적인데 클라이언트 수가 증가하면 힙 메모리 부족으로 인한 오류가 발생할 수 있다.

4) 논블로킹: 요청한 작업의 성공 여부와 상관없이 바로 결과를 돌려준다. 블로킹 소켓 대비해서 신경쓸 일이 많다는 것 뿐 큰 단점이 없다.

+ 일반적인 상황이라면 검증된 프로토콜을 사용하자.

특수한 상황이 아니고서야 네트워크 프로토콜 자체로 병목이 생기는 경우는 드물다. 대부분의 병목은 데이터를 핸들링하는 DB나 서버애플리케이션 내부 로직에 의해 발생한다. 물론 HTTP 같은 범용 프로토콜이나 이를 차용한 라이브러리를 사용하는 것이 부적합한 경우가 있다. 예를 들면 대용량 파일을 주고 받는다던가 금융/다중접속 게임의 데이터처럼 준-실시간 통신이 필요한 경우가 있을 것이다.

일반적인 상황이라면 검증된 프로토콜, 라이브러리를 쓰자. 우선 서버-클라이언트 구조에서 반복적으로 발생하는 패턴을 다시 구현하지 않아도 된다. 인코딩/디코딩, 직렬화, 에러/예외 처리 등이 사용자(개발자) 영역까지 넘어오지 않는다. 이런 것들을 직접 구현하게 되면 구현 과정에서 발생하는 버그는 차치하더라도 생산성 측면에서 마이너스 요소가 많다. (물론 더 나은 성능을 확보할 순 있겠지만.) 최근 여러 모바일 게임의 서버가 괜히 HTTP 기반으로 구현된게 아니다.


728x90
반응형
블로그 이미지

nineDeveloper

안녕하세요 현직 개발자 입니다 ~ 빠르게 변화하는 세상에 뒤쳐지지 않도록 우리모두 열심히 공부합시다 ~! 개발공부는 넘나 재미있는 것~!

,