실시간 서버 통신 구현

yeezze·2022년 12월 19일
0

form 프로젝트

목록 보기
5/7

개발

요구사항

클라이언트가 최초 요청을 1번 보낸 이후, 서버에서 1초에 한번씩 데이터를 동기화시키며 클라이언트에게 실시간 대기순번을 알려준다.

고민

spring 내부에서 실시간 순번을 로그로 찍어서 확인할 수 있도록 개발을 완료했다.
그럼 다음 단계로 로그로 찍는게 아닌, 다수의 클라이언트에게 어떻게 실시간으로 알려줄 수 있을까?

http는 stateless이다.
전통적인 http 방식으로는 클라이언트의 정보를 지속적으로 알지 못하는데 어떻게 구현할 수 있을까?

가장 중요한 핵심은

  • 클라이언트의 요청 없이 서버에서만 데이터를 보내준다
    • 클라이언트의 요청이 1초마다 계속 들어오게 되면 서버에 부하가 심해질 것이기 때문에 별도의 요청이 없는 방법으로 구현해야된다고 판단했다.
  • 서버는 다수의 클라이언트를 구분하여 데이터를 보내야한다.
    • 각 클라이언트마다 알맞은 대기 순번을 보내줘야한다.

해당 맥락을 기준으로 구현 방법을 찾아보았다.

구현

어떤 방법이 있을까?

양방향 통신

  • 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있다.
  • 여러 단말기에 빠르게 데이터를 뿌릴 수 있다.
  • request 없이 서버에서 데이터를 밀어넣어주는 기술을 통칭한다.
  • 우리가 주식차트를 볼 때 서버에서 차트로 주식 거래량과 가격을 항상 밀어넣어주는 기술이다.

웹소켓

웹 소켓은 클라이언트-서버 간 커넥션을 유지하기 때문에 이벤트를 주고받을 때 실시간성을 보장할 수 있다.
하지만 대용량 트래픽에서는 서버가 클라이언트 수만큼의 연결을 유지해야 한다는 부담이 있다.
트래픽이 한순간에 몰릴 가능성이 있는 현재 프로젝트에는 맞지 않는다고 생각했다.

SSE (Server Send Event)

서버와 한번 연결을 맺고나면 일정 시간 동안 서버에서 변경이 발생할 때마다 데이터를 전송받는 방법

SSE는 서버에서 클라이언트로 text message를 보내는 브라우저 기반 웹 애플리케이션 기술이며 HTTP의 persistent connections을 기반으로하는 HTML5 표준 기술이다.

SSE는 서버가 클라이언트로 비동기적인 데이터 전송을 할 수 있게 한다. 서버는 새로운 데이터가 업데이트될 때마다 그것을 클라이언트로 보낼지 결정할 수 있다. 서버에서 클라이언트로는 보낼 수 있지만, 클라이언트에서 서버로는 보내지 못 한다.

  • HTTP를 통해 통신하므로 다른 프로토콜이 필요없다
  • 표준기술이기에 대부분의 브라우저에서 지원된다.
  • 단방향이기에 편하다.

비동기적인 단방향! 클라이언트가 요청을 보내지 않고 서버에서 일방적으로 응답을 보낼 수 있다.
내가 원하는 것이다. HTML 표준 기술이기에 별도의 라이브러리 학습이 필요없다는 장점도 맘에 들었다. 결정했다!

개발

사전 테스트

먼저 작은 단위로 테스트를 진행했다.
test html 파일을 만들고 springboot 스케줄러와 연동하여 실시간 통신과 화면 변경이 원활하게 가능한지 테스트를 진행했다.

몇번 삽질 끝에 성공했다! 우와아 신기하다 ㅎㅎ

자세한 코드는 올리지 않고 몇가지 포인트만 정리해보려고 한다.
코드는 깃허브에 있다.

1. 유실될 가능성이 있는 이벤트를 관리하여 재전송해주자!

emitter에는 타임아웃이 설정되어 있기 때문에 서버에서 이벤트를 보내는 시점에 emitter가 만료되서 없는 경우가 생길 수 있다.
이때 서버에서 보낸 이벤트 데이터가 유실되는 에러가 발생한다.
해당 에러를 커버하기 위해서는 마지막에 보낸 이벤트를 관리하여 클라이언트로 재전송해주어야한다.

🔥 트러블 슈팅

로그를 살펴보면
7f06 유저에게 "응답 제출이 완료됐습니다."라고 이벤트 데이터를 전송했다.
그러나 밑에 로그를 살펴보면 전송 시점에는 emitter가 만료되었고 이벤트 데이터 전송 이후에 새로운 emitter가 생성됐다.

해당 유저의 브라우저를 확인해보니 sse 이벤트가 정상적으로 전송되지 않았다.
서버에는 응답 제출이 완료됐지만 클라이언트 쪽에서 이벤트 데이터를 못 받았기 때문에 유저는 나의 응답이 제출됐는지, 내 순서가 몇번인지 모른채 의문에 빠져있을 것이다.

🎄 문제를 해결..

event 전송 시 클라이언트 별로 고유 id를 설정하고
서버에서 eventStream 재생성 시에 클라이언트에 보낸 마지막 이벤트 데이터를 확인하여 재전송해주도록 구현했다.

last-event-id 헤더값을 활용하여 이벤트 데이터를 관리하고 재전송하는 방법도 있다.
last-event-id를 전송 시간과 조합하여 생성하면 그동안 서버에서 보냈던 이벤트를 전부 관리하고 선택적으로 재전송할 수 있을 것이다.

해당 프로젝트는 놓친 이벤트 데이터를 전부 다시 보여줄 필요는 없고 마지막 이벤트 데이터만 보여주면 되기 때문에 생성 시간에 따른 모든 이벤트 데이터를 관리하지는 않았다.
때문에 last-event-id도 별도로 사용하지는 않았다.

클라이언트와 통합테스트


2명 선착순 이벤트에 5명이 접속했다고 가정하고 테스트를 진행했다.

상단 브라우저 2명이 당첨되고 하단의 3명은 아쉽게도 선착순에 늦게 신청되어 당첨되지 못했다.
대기번호 숫자가 화면에 실시간으로 반영되어 정상적으로 동작하는 것을 확인할 수 있었다.

참고
https://velog.io/@mtak0235/실시간으로-소통하고-싶다면
https://go-coding.tistory.com/73
https://velog.io/@max9106/Spring-SSE-Server-Sent-Events를-이용한-실시간-알림
https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/

profile
백엔드 개발자 😊

0개의 댓글