티스토리 뷰

반응형

실시간 업데이트 기능 구현 방법

1. Polling

클라이언트가 주기적으로 서버에 요청을 보내는 방식이다.

구현이 단순하지만, 계속 요청하는 점에서 리소스 낭비가 발생할 수 있다.

2. Long Polling

클라이언트가 요청을 보내면, 서버에서 변경이 일어날 때까지 (이벤트 발생할 때까지) 대기한다.

Connection이 연결된 동안의 이벤트 발생을 실시간으로 감지한다.

주기적으로 요청을 보내지 않는다. (유지 시간 내 이벤트가 발생한다면)

 

유지 시간을 짧게 설정하면, Polling과 차이가 없다.

지속적으로 연결되어있어 순간적으로 부담이 증가할 수 있다.

3. WebSocket

Connection을 지속해 연결에 드는 비용 제거한다.

양방향으로 데이터를 주고받을 수 있다.

HTTP가 아닌 ws 프로토콜을 사용한다.

4. Server Sent Event(SSE)

클라이언트와 서버가 연결을 맺으면 Server의 이벤트를 클라이언트로 전송하는 단방향 통신이다.

HTTP 프로토콜만으로 사용할 수 있다.

자동 재연결이 가능하다.

 

WebSocket은 완전히 실시간으로 업데이트 받아야 하는 곳에 주로 사용하고, SSE는 알림 기능 등 완전히 실시간일 필요는 없는 곳에 주로 사용하다.

 

SSE 연결 과정

1. 클라이언트가 SSE 구독을 요청한다.

2. 서버가 이에 대한 응답을 한다.

Response mediaType은 text/event-stream이며, Transfer-Encoding 헤더 값을 chunked로 설정한다.

3. 서버가 이벤트를 전송한다.

각 이벤트는 한개 이상의 name:value로 구성된다.

 

구현하기

SSE 연결하기

val emitterMap = hashMapOf<Long, SseEmitter>()

@GetMapping("/connect/{id}", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) //(1)
fun connectSSE(@PathVariable userId: Long): ResponseEntity<SseEmitter> {
    val sseEmitter = SseEmitter(100_000) //(2)
    emitterMap.put(userId, sseEmitter)
    val sseBuilder = SseEmitter.event().name("dummy").data("data").reconnectTime(500)
    sseEmitter.send(sseBuilder) //(3)
    return ResponseEntity.ok(sseEmitter)
}

 

(1) MediaType.TEXT_EVENT_STREAM_VALUE: text/event-stream에 해당하는 문자열이다.

(2) SseEmitter의 타임아웃 시간을 100초(밀리초 단위)로 지정한다. (기본 30초)

만료시간이 너무 길다면, 서버에서 불필요한 커넥션을 관리해야 하고, 너무 짧으면, 재연결 요청이 잦아져 적당한 시간을 설정해야 한다.

(3) Emitter를 생성한 후, 더미 데이터와 재연결 시간을 보낸다.

Emitter를 생성한 후에 만료시간까지 아무런 데이터를 보내지 않으면, 재연결 요청시 503 Service Unavailable 에러가 발생한다.


SseEmitter는 클라이언트 요청에 생성해준다.

따라서, 사용자 ID 혹은 세션 ID 등의 정보와 함께 SseEmitter를 관리해야 할 필요가 있다.

SseEmitter 객체는 일회성으로 사용하기 때문에, 지정한 시간이 지나면 폐기 후 새로 객체를 만든다.

따라서, 클라이언트의 재요청 시 기존의 SseEmitter를 제거해줄 필요가 있다.

서버 이벤트 발송하기

fun sendEvent(sseEmitter: SseEmitter, sseEventBuilder: SseEventBuilder) {
    try {
        sseEmitter.send(sseEventBuilder);
    } catch (e: IOException) {
        sseEmitter.complete();
    }
}

 

connection이 끊기거나 연결이 불안정하면 IO Exception이 발생할 수 있다.

 

주의할 점

클라이언트가 받지 못한 이벤트 처리

SSE 재연결 시, 재연결되는 동안에 발생하는 서버 이벤트 혹은 connection이 끊겼을 때, 클라이언트가 받지 못한 이벤트가 생길 수 있다.

재연결 시에 이러한 이벤트들을 발송하기 위해 이벤트에 발송 시간 혹은 이벤트 번호 등의 정보를 토대로 이벤트를 다시 보내주도록 구현할 수 있을 것으로 보인다. (클라이언트가 마지막으로 받은 이벤트의 시간 정보를 보내는 등)

DB Connection 고갈 (open-in-view)

open-in-view는 api의 요청부터 응답까지 영속성 컨텍스트가 유지되도록 하는 설정이다.

Spring boot는 기본적으로 open-in-view가 true로 지정되어있다.

SSE가 연결되어있는 동안에 HTTP Connection도 계속 열려있기 때문에, 이 동안 DB 연결도 지속된다.

이 때문에 DB Connection이 고갈될 수 있다.

 

ConcurrentModificationException

SseEmitter 객체에 onError, onCompletion, onTimeError 콜백을 등록할 수 있는데, 이 콜백들은 다른 스레드에서 동작한다.

Thread-safe 하지 않은 Collection에 연산하지 않도록 해야 한다.

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함