Netty 개념

강정우·2024년 2월 4일
1

Dev_Ops

목록 보기
10/20

우선 네티는 자바로 구현한 소켓 프로그래밍과 똑같다.
다만, 네티만의 추상화로 인해 자바 소켓 프로그래밍과 그 궤를 달리한다.

Inbound, OutBound

우선 위 사진을 보면 Client와 Server의 Inbound와 Outbound가 서로 반대방향인 것을 확인할 수 있다.
이는 본인을 기준으로 데이터가 들어오면 Inbound 데이터가 나가면 Outbound이다.

하지만 서버 내부끼리의 데이터 전송은 OutBound

이게 굉장히 중요한데 이 한 문장을 얻기까지 꼬박 하루가 걸렸다.

spring-framework를 운용하고 내부에 다로 netty를 운용하여 spring으로부터 들어오는 요청은 netty 서버 내부에서 inbound로 처리되는 줄 알았다. 하지만 outboud로 처리된다.

Netty의 이벤트 모델에서 Inbound와 Outbound 이벤트는 네트워크 트래픽의 방향을 나타낸다.

Inbound 이벤트는 네트워크에서 서버로 들어오는 데이터를 가리키며, 이는 주로 클라이언트 또는 다른 서버 등 외부 요청에 의해 발생한다.
예를 들어, 사용자가 웹 브라우저에서 요청을 보내면, 이 요청은 서버로 들어오는 Inbound 이벤트로 처리된다.

반면, Outbound 이벤트는 서버에서 네트워크로 나가는 데이터를 가리키며, 이는 주로 서버의 응답 또는 서버의 내부 로직에 의해 발생한다.
예를 들어, 서버가 클라이언트에게 응답을 보내면, 이 응답은 네트워크로 나가는 Outbound 이벤트로 처리된다.

즉, @Service단의 비즈니스 로직에서 Netty 서버로 데이터를 보내는 경우, 이 데이터는 서버 내부에서 다른 부분(여기서는 Netty 서버)으로 이동하는 것이므로, 이는 "서버 내부에서 발생하는 이벤트"로 볼 수 있다는 것이다.
그리고 이 데이터는 최종적으로 네트워크를 통해 외부(클라이언트나 다른 서버 등)로 전송되는 것이 목적이므로, 이는 "서버에서 네트워크로 나가는 데이터"라고 할 수 있다.

즉, 서비스 로직이 처리 결과를 Netty 서버에 전달하고, 이 결과가 Netty 서버에서 클라이언트로 전송되는 이런 흐름 전체를 Outbound 이벤트로 보는 것이다.

spring과 netty를 함께 사용하는 이유?

Netty는 기본적으로 비동기 이벤트 기반의 네트워크 프로그래밍 프레임워크로서, TCP와 UDP, HTTP/2 같은 다양한 프로토콜을 지원하며, 고성능 및 고척도성을 가진 네트워크 애플리케이션을 구축하기 위한 기반 인프라를 제공한다.
따라서 Netty는 웹 서버, 프록시 서버, 채팅 서버, 게임 서버와 같은 다양한 유형의 네트워크 서비스를 구축하는데 사용될 수 있다.

그러나 Netty 자체는 웹 애플리케이션 개발에 필요한 많은 기능들 (예를 들어 데이터베이스 연결, ORM(Object-Relational Mapping), 템플릿 엔진, 인증 및 권한 관리, RESTful API 지원 등) 을 직접 제공하지는 않는다.

따라서 복잡한 웹 애플리케이션을 개발할 때는 Netty와 같은 네트워크 프레임워크와 Spring과 같은 백엔드 프레임워크를 함께 사용하는 경우가 많다.

이런 경우, Netty는 클라이언트와의 네트워크 통신을 담당하고, Spring은 비즈니스 로직 처리, 데이터베이스 연결 등의 역할을 수행한다.

실제로, Spring WebFlux는 Netty를 기본적으로 내장한 서버를 제공하며, 이를 통해 비동기 및 논블로킹 모델을 기반으로 한 웹 애플리케이션을 개발할 수 있다.

네티의 주요 특징

네티만이 갖고있는 개념이 있다.

1. 부트스트랩

우리가 흔히 아는 부트스트랩은 아마 웹사이트를 쉽게 만들 수 있게 도와주는 CSS, JS 프레임워크일 것이다.
하지만 네티에서의 부트스트랩은 네티로 작성한 네트워크 프로그램이 시작할 때 가장 먼저 수행되는 객체로서 하는일은 아래와 같다.

  1. 어플리케이션이 수행할 동작을 지정
  2. 프로그램에 대한 각종 설정을 지정한다.

부트스트랩 설정

그럼 부트스트랩에서 설정할 수 있는 것들에는 무엇무엇이 있을까?

  1. 이벤트 루프
    • 소켓채널에서 발생한 이벤트를 처리하는 스레드모델에 대한 구현이 담겨있음.
  2. 채널 전송 모드
    • 블로킹/논블로킹/epoll
  3. 채널 파이프라인
    • 소켓 채널로 수신된 데이터를 처리할 핸들러들을 지정한다.

2. 코덱

이벤트 핸들러를 상속받아서 구현한 구현체들이다. 자주 사용하는 이벤트 핸들러들을 미리 구현해둔 코덱 묶음은 io.netty.handler.codec 패키지에 있다.

대표적으로는 decoder, encoder가 있다.

주로 체널 파이프라인에 일부를 담당한다.

3. ChannelHandlerContext

  1. 채널에 대한 입출력을 처리한다.

writeAndFlush 메서드로 채널에 데이터 기록
close 메서드로 채널의 연결을 종료시킴

  1. 채널 파이프라인에 대한 상호작용

프로그래머에 의한 이벤트 발생
채널 파이프라인에 등록된 이벤트 핸들러의 조회 및 동적 변경

예를들어 데이터 수신 이벤트 처리 메서드에서 오류가 발생했고 오류처리 공통로직이 exceptionCaught 이벤트 메서드에 작성되어 있다면, fireExceptionCaught 메서드를 호출할 수 있다.
채널 파이프라인으로 exceptionCaught 이벤트 전달.

네티 동작과정

스프링 프로젝트가 뜰 때 netty 구성

  1. 부트스트랩으로 네트워크 어플리케이션에 필요한 설정 지정
  2. 부트스트랩에 이벤트 핸들러들을 사용하여 채널 파이프라인을 구성
  3. 이벤트 핸들러의 데이터 수신 이벤트 메서드에서 데이터를 읽어들임
  4. 이벤트 핸들러의 네트워크 끊김 예외처리 메서드에서 에러 처리

이후 데이터가 들어온 후 동작과정

  1. 사용자 요청 -> 백앤드 코드는 서버 연결 시도
  2. 서버는 클라이언트 연결에 대응하는 소켓 채널 객체 생성
  3. 연결이 완료되면 클라이언트가 데이터 송신
  4. 네티가 소켓 채널에서 읽을 데이터가 있다는 이벤트(데이터)를 채널 파이프라인으로 흘림
  5. 빈 채널 파이프라인 객체 생성 후 채널에 할당 채널에 등록된 ChannelInitializer 인터페이스의 구현체를 가져와 initChannel 메서드 호출
  6. 5에서 등록한 빈 채널 파이프라인을 가져와 커스텀 이벤트 핸들러 객체를 파이프라인에 등록
  7. 채널 파이프라인에 등록된 이벤트 핸들러 중, 인바운드 이벤트 핸들러가 해당하는 메서드 수행
  8. 요청 url에 맞춰 controller, service 로직 실행 service 로직에서 netty manger를 실행
  9. outbound를 돌고 나서 들어온 이벤트에 대해 sendDataProcessing 시작 (*이벤트 루프가 실행)
  10. 단말에서 처리 후 다시 netty에 데이터 전송
  11. 들어온 데이터는 inbound를 돌고 나서 receiveDataProcessing 시작 (*이벤트 루프가 실행)
  12. 해당 프로세서 맞춰 이후 로직 동작

이벤트 루프

이벤트 루프 : 이벤트를 실행하기 위한 무한루프 스레드

이벤트 큐에 이벤트를 등록하고 이벤트 루프가 큐에 접근하여 처리한다.
이벤트 루프가 다중 스레드이면 한 큐를 여러 스레드에서 공유해서 쓴다.

*데이터 수신 이벤트가 발생했을 때 이벤트루프가 이벤트를 처리하는 과정
1. 네티의 이벤트 루프가 채널 파이프라인에 등록된 이벤트 핸들러를 가져옴
2. 해당 핸들러에 데이터 수신 이벤트에 대한 메서드가 구현되어있다면 실행
3. 해당 메서드가 없다면 다음 핸들러 가져옴
4. 채널 파이프라인에 등록된 마지막 이벤트 핸들러까지 위 과정 반복

이벤트 핸들러

소켓 채널에서 발생한 이벤트를 처리하는 인터페이스
채널 파이프라인에 등록되어 파이프라인으로 들어오는 이벤트를 이벤트루프가 가로채어 알맞은 메서드를 수행한다

채널 파이프라인에 여러 개의 이벤트 핸들러가 등록되어 있을 때의 동작
1. 등록된 이벤트 핸들러가 없다면 무시된다.
2. 등록된 이벤트 핸들러가 한 개이면 해당 핸들러가 적용된다.
3. 등록된 이벤트 핸들러가 여러 개이면 가장 앞에 있는게 적용되고 뒤로 전달되지 않는다. (이게 중요!!)
4. 다음 이벤트 핸들러로 이벤트를 넘겨주는 방법은 channelHandlerContext.fireChannelRead 메서드 호출. 채널 파이프라인에 해당 이벤트를 발생시킨다.

네티는 소켓 채널에서 발생하는 이벤트를 '인바운드 이벤트'와 '아웃바운드 이벤트'로 추상화한다.

1. 인바운드 이벤트

연결 상대방이 어떤 동작을 취했을 때 발생 (ex 채널 활성화, 데이터 수신 등)

네티는 인바운드 이벤트 핸들러를 ChannelInboundHandler 인터페이스로 제공한다.

  1. channelRegistered
    채널이 이벤트루프에 등록되었을 때 발생
    새로운 채널이 생성되는 시점에 발생
    서버에서는 서버 소켓 채널 생성시, 클라이언트 소켓 채널 생성시 발생
    클라이언트 에서는 connect 메서드를 수행할 때 (클라이언트 소켓 채널 생성시) 발생

  2. channelActive 이벤트

  3. channelRegistered 이후에 발생
    채널이 생성되고 이벤트 루프에 등록된 후, 네티 API 를 이용하여 채널 입출력을 수행할 상태가 되었음을 알려주는 이벤트
    서버 또는 클라이언트가 상대방에 연결한 직후에 한번 수행할 작업을 처리하기에 적합

  4. channelRead 이벤트
    데이터가 수신되었음을 알려주는 이벤트
    수신된 데이터는 ByteBuf 객체에 저장되어 있음

  5. channelReadComplete 이벤트
    데이터 수신이 완료되었음을 알려주는 이벤트
    채널의 데이터를 다 읽어서 더 이상 데이터가 없을 때 발생

  6. channelInActive 이벤트
    채널이 비활성화 되었을 때 발생
    이 이후에는 채널에 대한 입출력 작업을 수행할 수 없음

  7. channelUnRegistered 이벤트
    채널이 이벤트 루프에서 제거되었을 때 발생
    이 이후에는 채널에서 발생한 이벤트를 처리할 수 없음

2. 채널 아웃바운드 이벤트

소켓 채널에서 발생한 이벤트 중 프로그래머가 요청한 동작에 해당하는 이벤트

채널 아웃바운드 이벤트를 처리하는 핸들러 구현을 위해 ChannelOutboundHandler 인터페이스를 사용한다.
이 인터페이스의 핸들러 메소드들은 ChannelHandlerContext 객체를 인수로 받는다.

  1. bind 이벤트
    서버 소켓채널이 클라이언트의 연결을 받아들이는 ip,port 정보 설정시 발생

  2. connect 이벤트
    클라이언트 소켓채널이 서버에 연결되었을 때 발생

  3. disconnect 이벤트
    클라이언트 소켓채널의 연결이 끊어졌을 때 발생

  4. close 이벤트
    클라이언트 소켓채널의 연결이 닫혔을 때

  5. write 이벤트
    소켓채널에 데이터가 기록되었을 때 발생

  6. flush 이벤트
    소켓채널에 대한 flush 메서드 호출시 발생

reference

https://velog.io/@monami/Netty

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글