Netty?
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
netty는 JAVA 진영의 Transport Layer에서 가장 유명한(?) 프레임워크이다.
TCP 뿐만 아니라 HTTP, UDP 등 사실상 byte를 송/수신하는데에 두루두루 쓰이고 있는 상황이다.
수 많은 프레임워크들이 클러스터링 구성을 하거나 할 때 내부 통신(inner-communication)을 하는 프레임워크로 netty를 주로 사용하고 있다.
내부 구현은 JAVA NIO로 되어 있으며, 플랫폼에 따라 Epoll(Linux)이 추가로 Socket을 사용할 수 있도록 구현되어 있다
고성능/경량 HTTP 서버를 구축할 수 있는 Node.js의 경우의 동작 시퀀스가 위와 같은데, netty도 이와 같은 구조를 채택하고 있다.
하나의 EventLoop라는 Acceptor가 들어오는 incoming request에 대한 I/O를 담당하고(single thread) 해당 작업에 대한 처리는 worker thread에서 수행하게 한다.
구글의 RPC 프레임워크인 gRPC에서도 netty를 내부 transport로 사용하고 있다.
도식에서 보다시피 하나의 EventLoop에서 여러 request를 받아 worker thread로 분산시켜주는 것을 알 수 있다.
이와 같은 구조로 얻는 이점은
- 자원 사용 효율성 증대(적은 오버헤드, low context switching)
- CPU 코어 사용 증대(default로 worker thread는 CPU Core * 2로 잡힌다)
위와 같은 구조로 개발을 할 수 있게 되었다.
큰 차이점은 Client의 Connection 수립마다 Thread를 생성하지 않아도 된다는 점이다.
Selector라는 클래스가 가지고 있는 Key에 대한 I/O 이벤트를 감시하고 있으며, Accept, Read, Write, Connection Closed 와 같은 이벤트를 감시해서 non-blocking하게 동작한다.
try (final Selector selector = Selector.open();
final ServerSocketChannel serverSocket = ServerSocketChannel.open();) {
final InetSocketAddress hostAddress =
new InetSocketAddress(Constants.HOST, Constants.PORT);
serverSocket.bind(hostAddress);
serverSocket.configureBlocking(false);
serverSocket.register(selector, serverSocket.validOps(), null);
while (true) {
final int numSelectedKeys = selector.select();
if (numSelectedKeys > 0) {
handleSelectionKeys(selector.selectedKeys(), serverSocket);
}
}
}
위 아키텍쳐에서 보다시피, Network I/O에 Thread pool을 적용했고 Worker Thread를 Socket I/O에 적용했다.
incoming 데이터는 Inbound Handler에서 처리하고, outgoing 데이터는 Outbound Handler에서 처리하도록 짜여져 있다.
Spring Boot?
사실 이건 Transport 프레임워크가 아니다.
이름에서 보다시피 "Spring" 프레임워크이며 HTTP Servlet을 다루기 위한 프레임워크인데 왜 사용했냐!? 라고 하면 다음의 이유이다.
- 프레임워크의 높은 확장성으로, 여러 모듈을 활용할 수 있다(JPA, Batch, Scheduling, AMQP .. 등)
- 개발할 때 Singleton을 일일히 만들면서 static class를 관리할 포인트를 줄여준다.
- Manager Class의 경우 하나씩만 생성이 되야하는 부분이고, 개발할 양이 많아질 수록 관리하기 힘들다.
- Netty가 Spring의 Singleton 방식의 Bean을 지원한다.
- @ChannelHandler.Sharable 어노테이션으로 하나만 생성되서 사용할 수 있다
FlatBuffers?
2016/11/18 - [Development/Java Netty & FlatBuffers] - [Flatbuffers] 플랫버퍼란?
을 참고하도록 하자.
이 라이브러리는 Packet의 Serialization(직렬화) / Deserialization(역직렬화) 에 사용한다.
통신 프로그램에서는 데이터를 어떻게 보내고 받을것인지에 대한 규약이 있어야 하는데
대부분 그냥 byte[] 로 보내게 되면 이걸 String으로 보내는지.. Object를 byte화 해서 보내는지.. JSON으로 변환해서 보내는지 등의 여러 방법이 있다.
그 중 성능이 뛰어난 Flatbuffer는 메세지를 미리 정의해두고, 해당 메세지를 컴파일하게 되면 언어에 맞는 클래스로 자동 변환을 해주는 기능을 지원한다(IDL)
따라서 개발할 때 패킷의 구조 변경이나 새로운 패킷을 추가할 때 엄청난 유연성을 제공하게 된다.
예로 들면 Flatbuffer를 사용하지 않을 경우 패킷의 변경이 있을 때이다.
- Server쪽 JSON Object 내부 field를 변경
- Client쪽에서 JSON Object의 내부 field를 변경(C#)
- Client쪽에서 JSON Object의 내부 field를 변경(C++)
- Client쪽에서 JSON Object의 내부 field를 변경(Go)
- 송/수신할때 로직 처리 분기 추가
- Server쪽에서 메세지 포맷을 변경
- Flatbuffer IDL로 언어별 클래스를 생성
- 각 클라이언트 언어에 맞게 배포 진행
- 송/수신할 때 로직 처리 분기 추가
해서 위의 세가지 프레임워크&라이브러리를 사용해서 통신 프로그램을 만들어 보는 것이 목적이다.
채팅 or 게임 or inner communication 등의 여러 목적으로 쓰이는 것을 고려하여 확장성 있게 개발해 보는 것을 취미(?) 삼아..
프로젝트는 open-source로 github에 공개되며 주소는 ==> https://github.com/Gompangs/GNetServer
근데 코드를 직접 설명을 해야하나 싶긴한데
필요한 부분 부분 설명을 해야할 듯 하다.
(flatbuffer 프로젝트 연동해서 자동 빌드 환경 구축하기, netty 설정, packet dispatch 로직 구성 등)
일단 구상해본 서버 구성도는 위와 같다(사실상 netty component에 서비스를 추가해나가는 확장 구조이므로 크게 별건 없다)
p.s
원래 목적은 netty를 사용한.. 채팅이나 게임 서비스의 틀을 잡으려고 했는데
이게 비지니스 로직이 더 많은 프로젝트가 되면서 포스팅을 멈추게 되었다.
내용을 보면.. 핸들러와 flatbuffer 보다는 서비스 로직을 만드는 부분에 디펜던시가 커지고 있는 걸 느낌..
그만큼 netty를 사용하기가 간단하다는 소리다.
통신에 관련된 부분보다는 로직에 관련된 부분에 집중을 할 수 있게 해주니 말이다..
위의 구조까지 개발된 부분은 github에 있으니 참고하면 좋을 듯 하다.
'Development > Netty & FlatBuffers' 카테고리의 다른 글
FlatBuffers 빌드 자동 구성 (0) | 2018.04.15 |
---|---|
[Flatbuffers] 플랫버퍼란? (36) | 2016.11.18 |