지난 글에 이어서 실시간 대기 순번을 받을 수 있도록 구현해 보자.
일반적으로는 API 는 클라이언트가 요청한 값에 대해 응답을 제공한다.
클라이언트(웹 애플리케이션, 모바일 앱, 서버)가 특정 데이터를 요청하면, API는 그 요청에 알맞은 데이터를 반환한다.
API는 클라이언트의 요청에 대해서만 응답을 반환하며, 클라이언트의 요청 없이 API가 데이터를 보내지 않는다.
하지만 API가 클라이언트의 요청 없이 데이터를 보내야 하는 상황이 있다. 지금과 같이~~
이렇게 API가 클라이언트의 요청 없이 데이터를 주도적으로 전달하기 위해 서버-사이드 이벤트(SSE), 웹소켓, 푸시 알림, 폴링/롱 폴링과 같은 기술들이 개발되었다.
실시간 알림을 구현하는 대표적인 방법에는 SSE, WebSocket 가 있다.
위 세 방식의 차이를 알아보자.
- WebSocket
클라이언트와 서버 간의 양방향 통신을 제공하는 프로토콜로, 서버에서 클라이언트로 실시간으로 데이터를 전송할 수 있다
양방향 통신이 필요한 게임, 채팅, 실시간 알림에서 주로 사용된다.
연결 유지에 비용이 들어가며, 트래픽의 양이 많아질 경우 여러 클라이언트에 대한 연결을 관리해야 하기 때문에 서버에 부담이 된다.
각 연결마다 소켓을 유지해야 하기 때문에 자원 소모가 커질 수 있다. - SSE (Server-Sent Eevents)
서버에서 클라이언트로 실시간으로 이벤트를 전송하는 방식으로, 단방향 통신만 지원한다.
한 번만 연결되면 계속해서 데이터를 전송할 수 있어 유지보수가 용이하다. 연결이 유지되어 있는 동안 서버는 데이터를 계속 보낼
수 있다. 연결이 끊어지면 클라이언트는 자동으로 재연결을 시도할 수 있다.
연결이 종료될 때까지 서버에서 웹 브라우저로 데이터를 계속 보낼 수 있다.
내가 필요한 거
일단, 내가 구현하는 알림 기능은 서버에서 클라이언트로 단방향으로만 동작한다.
즉, 클라이언트에서 단순히 알림에 대한 정보만을 제공 받고 서버로 데이터를 전송하지는 않는다.
양방향 통신일 필요가 없다고 판단하여 SSE를 선택하였다.
실시간 대기 순번
실시간으로 자신의 대기 순번을 확인할 수 있어야 한다. Redis와 SSE (Server-Sent Events)를 이용해 예매 대기열에서 유저를 관리하고, 예매 화면으로 진입할 시 해당 유저에게 실시간으로 알림을 보내도록 하였다.
주요 메서드를 살펴보자.
1. sendEvents
![](https://blog.kakaocdn.net/dn/Bk3sn/btsLDmX33LZ/ynmPVhkiVaQDetyCLZO060/img.png)
- @Scheduled(fixedDelay = 1000)에 의해 1초마다 호출된다.
- enterPageFromQueue(), getQueuePosition()가 차례로 호출된다.
2. enterPageFromQueue
![](https://blog.kakaocdn.net/dn/04bW1/btsLDU7Toe9/b5vrBgVr3aFkeMKH6NjUgK/img.png)
- 대기열에 있는 유저들을 예매 화면으로 진입시키는 메서드이다.
- Redis의 ZSet에서 특정 범위(start ~ end)에 해당하는 유저들을 가져온다.
- 차례가되면 집입 알림을 전송하고, Redis에서 해당 유저를 제거한다.
- sendEntryNotificatiton(user.toString())를 호출하여 해당 유저에게 알림을 보낸다.
3. getQueuePosition
![](https://blog.kakaocdn.net/dn/cvt4Yy/btsLEswzVAW/2PucXrKEp4qBCmCmPNyyjk/img.png)
- Redis에서 대기열에 있는 유저들을 조회하고, 특정 유저의 현재 순번을 찾아 반환하는 메서드이다.
4. sendEntryNotificatiton
![](https://blog.kakaocdn.net/dn/bJdsb6/btsLDtXigWp/i2CFismnkVOimOAJ6kXixK/img.png)
- 유저에게 예매 화면 진입 알림을 보내는 메서드로 SseEmitter를 통해 실시간 알림을 보내며, 예매 화면으로 진입할 준비가 되었음을 알려준다.
- sseEmitters.get(email)로 유저의 SseEmitter 객체를 가져와, 해당 유저가 연결되어 있으면 emitter.send()로 실시간 알림을 보낸다.
- emitter.complete()를 호출하여 알림 전송을 완료하고, sseEmitters.remove(email)로 해당 유저의 SseEmitter 객체를 제거한다.
- 예외가 발생하면 emitter.completeWithError(e)로 에러를 처리하고, 에러 로그를 출력한다.
5. createEmitter
![](https://blog.kakaocdn.net/dn/cvNWtu/btsLDo2EXc0/mjZ2SFBUWvIJ355PdAtPpk/img.png)
- SseEmitter를 통해 각 유저와 실시간 연결을 유지하며, 알림을 전송한 후 연결을 종료한다.
- onCompletion()과 onTimeout()은 연결이 완료되거나 타임아웃이 발생했을 때 해당 유저의 SseEmitter 객체를 제거한다.
테스트
![](https://blog.kakaocdn.net/dn/Nixj5/btsLC5Wxi58/UQK5wqsX8IIee0XjQQ5hB1/img.png)
Kafka 메시지 대기열 시스템을 멀티스레드 환경에서 검증하는 테스트로, 여러 사용자가 동시에 대기열에 메시지를 생성해도 Kafka 프로듀서가 정상적으로 작동하는지 확인한다.
100개의 스레드를 생성하여 각 스레드가 Kafka 프로듀서를 통해 특정 게임 ID와 이메일 기반으로 메시지를 대기열에 추가하는 작업을 테스트한다.
각 스레드는 producer.create(gameId, email)을 호출하며, 발생 가능한 예외를 로깅한다. 모든 요청이 정상적으로 처리되었는지는 CountDownLatch를 통해 확인하고, 작업 완료 후 모든 스레드가 정상 종료되었는지도 검증한다.
결과
![](https://blog.kakaocdn.net/dn/ba9rE0/btsLEqlaRdM/8t8wyu148sWDpVvDXVa651/img.png)
![](https://blog.kakaocdn.net/dn/nVHgR/btsLC6usqKd/0XKywg1Os6xNyYcPxSkL90/img.png)
'Project' 카테고리의 다른 글
[KBOTicket] 도커로 애플리케이션 배포하기 (0) | 2025.01.07 |
---|---|
[KBOTicket] CI/CD의 개념과 구현 (0) | 2025.01.07 |
[KBOTicket] Kafka를 이용한 티켓팅 대기열 시스템 구현 및 순번 처리 (1) (0) | 2025.01.06 |
[KBOTicket] 좌석 선점 기능 구현 시 동시성 처리하기 (0) | 2025.01.06 |
[KBOTicket] JWT를 활용한 인증 과정과 구현 (0) | 2025.01.06 |