sse를 이용한 실시간 알림 도입기

213kky·2024년 8월 22일
post-thumbnail

최근에 새로운 프로젝트를 참여하며 알림기능 부분을 맡아 구현하기로 하였다.

알림을 보내는 방식에는 Polling, Long Polling, SSE, WebSocket등 여러 선택지가 있었다.
각각의 특징을 살펴보고 SSE를 도입한 이유를 작성해 보려 한다.

Polling

특정 주기를 가지고 서버에 요청을 하는 방식이다

  1. 클라이언트에서 서버로 요청을 보낸다.
  2. 서버는 응답을 보낸다.
  3. 클라이언트는 응답을 받은 뒤 일정시간 대기한다.
  4. 위 과정을 반복한다.

서버에 변경사항이 없어도 클라이언트는 계속 요청을 보내고, 이로 인해 서버에 부담을 주게 된다.
실시간은 아니며 실시간과 최대한 가깝게 구현하기 위해서는 요청 주기를 짧게 잡아야하며 비효율적이다.
실시간성이 중요하지 않다면 사용할 수 있다.

Long Polling

클라이언트가 서버로 요청을 보내고 데이터를 가져오는 것이 불가능 할 경우, 서버가 즉각적으로 반응하지 않고 특정시간을 기다리다 어떤 이벤트나 데이터 접근이 가능해지면 그때 서버의 데이터와 함께 응답하는 방식을 의미한다.

  1. 클라이언트에서 서버로 요청을 보낸다.
  2. 서버는 바로 응답하지 않고 기다리다가 이벤트가 생기면 응답을 보낸다.
  3. 위 과정을 반복한다.

long polling 방식은 클라이언트와 연결을 유지하며 변경된 데이터가 발생하거나 정해진 타임아웃 시간이 지나면 데이터를 전송한다.

short polling 방식과 비교하면 클라이언트가 불필요하게 많은 요청을 생성하지 않을 수 있다.
이벤트가 발생하면 응답하기에 실시간성이 어느정도 보장된다. 하지만 서버로직이 다소 복잡해지고, 자원도 많이 소모하게 된다.

실시간 메세지 전달이 중요하지만 서버의 상태가 빈번하게 변경되지 않는 경우에 적합한 방식이다.

SSE

Server Sent Event 방식은 클라이언트와 서버가 한번 연결을 맺고 난 후 일정시간 동안 서버에서 변경이 발생할 때마다 데이터를 전송받는 방법입니다.

  1. 클라이언트에서 연결 요청을 한다.
  2. 서버는 연결을 유지하며, 특정 시간 동안 또는 클라이언트가 연결을 끊을 때까지 발생하는 이벤트에 대해 실시간으로 데이터를 전송한다.

주기적으로 요청을 보내야 하는 Polling에 비해 효율적이며,
Long Polling 방식에 비해 Server Sent Event는 다시 요청을 안해도 된다는 장점을 가지고 있습니다.
HTTP위에서 동작하며 WebSoket 보다 구현이 간편하지만 단방향 통신이다.

WebSocket

WebSocket 방식은 클라이언트와 서버가 HTTP 기반으로 HandShaking을 한 후 ws프로토콜을 통해 상호간 응답을 하는 방법이다.

  1. 클라이언트는 HTTP 프로토콜을 사용하여 서버에 WebSocket 연결을 요청한다.
  2. 서버는 요청을 확인하고, 연결을 승인하면 HTTP 응답으로 WebSocket 프로토콜로 전환을 완료한다.
  3. WebSocket이 연결되면, 클라이언트와 서버는 지속적으로 열린 연결을 통해 양방향으로 데이터를 주고받을 수 있다. 이 과정에서 추가적인 요청/응답 과정 없이 실시간 통신이 가능하다.
  4. 연결은 클라이언트나 서버 중 하나가 명시적으로 닫을 때까지 유지된다. 연결을 닫기 위해 Close 프레임을 전송하며, 이때 상태 코드를 포함할 수 있다.

웹소켓은 채널을 이용해 양방향 통신을 가능하게 한다. 기본적으로 HTTP는 단방향 통신이기 때문에 서버가 먼저 클라이언트에게 응답할 수 없지만 웹소켓은 양방향 통신이 가능하다.

기존 http요청 응답 방식은 요청한 그 클라이언트에만 응답이 가능했는데, ws 프로토콜을 통해 웹소켓 포트에 접속해 있는 모든 클라이언트에게 이벤트 방식으로 응답한다.

최초 접속이 일반 http request를 통해 HandShaking과정을 통해 이루어 지기 떄문에, 기존의 80, 443 포트로 접속을 하므로 추가로 방화벽을 열지 않고도 양방향 통신이 가능하고, http 규격인 CORS적용이나 인증등의 과정을 기존과 동일하게 가저갈 수 있는것이 장점이다.

결론

Polling은 실시간 통신이 아니며, 실시간에 가깝게 구현하기 위해서는 서버에 주기적으로 많은 요청을 보내야 한다. 실시간성을 높일수록 더 많은 자원이 소모되어 비효율적이라고 판단하였다.

Long Polling은 실시간 통신이 가능하지만, 알림과 같은 서비스에서는 빈번한 데이터 전송으로 인해 많은 요청이 발생하여 비효율적이라고 판단하였다.

WebSocket은 실시간 양방향 통신이 가능하지만, SSE에 비해 무겁고 구현이 더 복잡하다. 또한, 프로젝트에서 구현하려는 알림 서비스는 양방향 통신이 필요하지 않으므로, 보다 가벼운 SSE를 사용하는 것이 적합하다고 판단하였다.



이후 구현도중 발생한 문제들

java.lang.NullPointerException: Cannot invoke "org.apache.catalina.Context.decrementInProgressAsyncCount()" because "this.context" is null
는 springboot 버전 3.1.5 이후 수정되었다. (현재 버전 3.1.4)


  1. sse의 complete() 메소드는 전송 중 오류 등 컨테이너 관련 이벤트가 발생한 후 사용해서는 안 된다고 한다.

  2. broken pipe등의 예외는 예외를 던지지 않고 emitter.onError()에서 객체를 제거하는 방식으로 처리해야 정상적으로 동작한다.

profile
since 2022

0개의 댓글