티스토리 뷰
채팅 솔루션을 개발하며 ChatGPT처럼 한글자씩 나오는 비밀이 무엇일까 찾아보았고 그 내막에는 SSE 기술을 사용한다는 사실을 알게되었다. 따라서 SSE에 대해 공부하고 가능하다면 실제 구현까지 진행해 보도록 할 예정이다.
SSE(Server-Sent Events) 방식이란?
SSE는 서버에서 클라이언트에게 메세지를 비동기적으로 전송할 때 사용하는 기술이다. 서버에서 클라이언트에게 전송해야하는 상황은 알림이나 채팅창이라든지 실시간으로 서버의 변경을 클라이언트에게 전달해야할 때 유용하다. 서버의 정책을 클라이언트에게 전달해야할때 사용해도 될 것 같다. 폴링(Polling)과 같이 클라이언트가 지속적으로 확인하는 방식이 있지만 좀더 실시간으로 반영하고 빈번하게 발생될 수 있는 케이스라면 SSE를 고려해볼 만하다.
SSE가 동작되는 과정은 다음과 같다.
- 클라이언트는 서버와의 연결을 요청 한다.
- mediaType은 text/event-stream으로 보낸다.
- 서버는 연결을 받아드린다.
- Transfer-Encoding 방식을 chunked로 한다. chunked는 데이터의 사이즈를 계산하는 과정을 거치지 않기 때문에 큰 데이터를 응답할때 효과적이다. 서버에서는 요청에 대해 응답을 계산할때 시간이 소요된다. Chunked를 통해 이와 같은 과정을 생략하는 것이다.
- 응답 패킷의 해더에는 Content-Length가 존재하지 않는다.
- 이후 비동기 적으로 클라이언트에게 이벤트를 전송한다.
- 데이터는 utf-8로 인코딩된 텍스트 데이터만 가능
- 각 이벤트는 한 개 이상의 name:value로 이루어져 있다.
Step1. SSE 구독 신청 (Client → Server)
클라이언트가 서버와의 연결을 요청하는 것을 구독이라는 행위로 나타낼 수 있다. 서버의 변화를 구독하여 실시간으로 관찰하고 싶다는 의미다. 우선 서버에게 구독을 요청하는 컨트롤러를 만든다.
@GetMapping(path="/subscribe/{id}", produces = MediaType.*TEXT_EVENT_STREAM_VALUE*)
public SseEmitter subscribe(@PathVariable Long id) {
return streamResponseService.subscribe(id);
}
컨트롤러에는 produces로 Content-Type을 MediaType.TEXT_EVENT_STREAM_VALUE 으로 지정하였다. 이렇게 지정하면 서버에서 응답 패킷의 헤더의 Content-Type이 text/event-stream으로 되고 텍스트 데이터를 연속해서 보낼 수 있다.
Step2. SSE 연결 수립 (Server)
1) SseEmitter 객체 생성
가장 먼저 클라이언트의 요청에 따라 서버에서는 통신 객체인 SseEmitter를 생성한다. 생성할때 만료 시간을 입력할 수 있다. 만료 시간이 너무 길면 연결 관리를 해줘야하고 짧으면 재연결 요청이 잦게 일어나므로 적절하게 설정 해야한다.
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
2) 더미 데이터 전송
서버와의 연결을 맺을때 더미 데이터를 넘겨줘야 한다. 그렇지 않고 데이터를 하나도 전송하지 않으면 재연결 요청시 503 Service Unavaliable 에러가 발생할 수 있다. 따라서 이를 방지하기 위해 Dummy 데이터를 보낸다. 여기서는 "EventStream Created, [UserId=" + id + "]" 의 데이터를 전송한다.
@Override
public SseEmitter subscribe(Long id) {
SseEmitter emitter = createEmitter(id);
sendMessage(id, "EventStream Created, [UserId=" + id + "]");
return emitter;
}
@Override
public void sendMessage(Long id, Object data) {
SseEmitter emitter = emitterRepository.get(id);
if (emitter != null) {
try {
emitter.send(SseEmitter.event()
.name("firstSSE")
.data(data)
.reconnectTime(RECONNECTION_TIMEOUT)
);
} catch (IOException e) {
// 보완
emitter.complete();
}
}
}
실제 연결을 수립할 때 다음과 같이 화면에 출력된다.
3) Emitter 객체 관리
Emitter 를 저장하기 위해 Spring Data JPA 를 사용해서 물리적으로 저장해도 된다. 하지만 HashMap을 사용한 케이스가 많아 HashMap을 사용했다. HashMap을 사용하면 데이터 저장 위치를 해시함수를 통해 바로 알 수 있어 검색이 빠르다. 일반 HashMap은 Thread-safe 하지 않으므로 ConcurrentHashMap을 사용한다. 멀티 쓰레드 환경에서 데이터를 관리하기 위해 동기화 과정이 필요하다. 이를 위해 쓰레드가 접근하는 Map에 Lock을 걸어야하는데 ConcurrentHashMap 에서는 Bucket 단위로 관리가 되어 오버해드를 줄일 수 있다는 장점이 있다.
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
Step3. 클라이언트에게 메세지 보내기 (Server → Client)
이제 마지막으로 연결되어있는 클라이언트에게 메세지를 전달하는 컨트롤러를 만들었다.
sendMessage에 id와 전달할 data를 인자로 주어 해당 id의 emitter에 메세지를 전달한다.
@GetMapping(path="/sendMessage/{id}/{data}")
public void sendMessage(@PathVariable Long id, @PathVariable String data) {
streamResponseService.sendMessage(id, data);
}
추가적으로 보낸 데이터가 계속해서 화면에 출력되는 것을 확인 할 수 있다.
이렇게 SSE 동작 과정에 대해 공부했고 다음에는 이를 사용해서 화면에 ChatGPT처럼 출력되도록 구현해보도록 하겠다.
참고
https://akku-dev.tistory.com/85
https://medium.com/@AlexanderObregon/implementing-server-sent-events-sse-with-spring-cbf283171aef
https://amaran-th.github.io/Spring/[Spring] Server-Sent Events(SSE)/#sse의-통신-과정
https://velog.io/@alsgus92/ConcurrentHashMap의-Thread-safe-원리
https://velog.io/@no-oneho/Spring-Boot로-SSE를-통한-알람-구현하기
https://velog.io/@wnguswn7/Project-SseEmitter로-알림-기능-구현하기#️-알림-repository
https://velog.io/@dhk22/TIL-Day
https://devel-repository.tistory.com/31
https://dkswnkk.tistory.com/702
https://sothoughtful.dev/posts/sse/
'Programming > Java' 카테고리의 다른 글
[Design Pattern] Singleton 패턴 (0) | 2023.12.15 |
---|---|
[SpringBoot] AOP를 활용하여 컨트롤러 메소드 단위 IP 제약 (0) | 2023.09.11 |
[Springboot] @ControllerAdvice 이용하여 NoHandlerFoundException처리 + thymeleaf (0) | 2023.09.08 |
[Mybatis] foreach를 사용하여 update 하기 (0) | 2023.08.28 |
[java] 싱글톤 기초 패턴 (0) | 2022.07.10 |
- Total
- Today
- Yesterday
- 코딩테스트
- llm
- Ai
- lightsail
- 자료구조
- springboot
- 보안
- 그리디
- 정보보안
- 정보보안기사
- FastAPI
- 우선순위큐
- 백준
- 카카오페이면접후기
- java
- 분산시스템
- 보안기사
- synflooding
- 다이나믹프로그래밍
- 리눅스
- t-test
- LangChain
- 파이썬
- 프로그래머스
- 시간초과
- Python
- linux
- 카카오페이
- t검정
- 딥러닝
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |