-
Notifications
You must be signed in to change notification settings - Fork 0
Long Polling으로 랜덤매칭
HeeJu Cho edited this page Oct 26, 2023
·
4 revisions
사용자가 채팅 버튼을 누르면, 랜덤으로 상대방이 매칭되어 채팅방이 생성되어야 합니다.
중요한 요구사항은 다음과 같습니다.
- 실시간으로 접속되어있는 사용자끼리 1:1 매칭할 것
- 같은 그룹의 사용자끼리 매칭할 것
- 차단한 사용자와는 매칭하지 않을 것
랜덤매칭 기능을 작업 흐름은 다음과 같습니다.
- 사용자가 랜덤채팅 요청 시 대기열에 저장
- 대기열에서 매칭 상대방 찾기
- 매칭되면 채팅방 생성 후 채팅방 id 반환
- 매칭 실패하면 422 코드 반환
- 422 코드를 받으면 클라이언트에서는 사용자에게 매칭에 실패했음을 알리고, 재요청 할 것인지 아니면 나갈 것인지 물음
가장 먼저 고려한 방식은 SSE입니다.
SSE는 서버와 클라이언트가 한 번 연결을 맺고 나면, 일정시간 동안 서버에서 클라이언트로 데이터를 전송할 수 있습니다.
타임아웃 시 브라우저에서 자동으로 서버에 재연결 요청을 보내기 때문에 Long Polling이나 Polling보다 효율적입니다.
매칭 실패 시 클라이언트에서는 "매칭 상대방을 찾을 수 없습니다!"라는 메세지를 띄우고 사용자에게 재요청 할 것인지 아니면 나갈 것인지 묻습니다.
이를 위해 타임아웃 시 예외코드를 반환합니다. 실패응답을 클라이언트에서 별도로 처리하지 않으면 SSE에서는 자동으로 재연결 요청을 보내 사용자가 랜덤 매칭을 무기한으로 기다리게 됩니다.
emitter.onTimeout(() -> {
emitter.completeWithError(new CustomException());
});
타임아웃마다 클라이언트가 재요청을 한다면 Long Polling과 비교해 SSE의 장점이 없어지기 때문에 SSE에서 Long Polling으로 적용기술을 변경했습니다.
private final Map<String, DeferredResult<Map<String, Object>>> que = new ConcurrentHashMap<>();
// 타임아웃 시 422 반환
DeferredResult<ResponseEntity<Map<String, Object>>> deferredResult =
new DeferredResult<>(10 * 1000L,
ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(Map.of("message", "매칭에 실패했습니다."));
// 요청 시 que에 저장
que.put(key, deferredResult);
// 랜덤매칭 로직
var result = chatService.matching(groupId, memberId);
// 매칭 성공 시, 본인과 매칭 상대방 성공 응답
if (result.geChatRoomId != 0L) {
deferredResult.setResult(ResponseEntity.ok().body(Map.of("chatRoomId", result.getChatRoomId)));
var partnerDeferredResult = que.get(partnerKey);
partnerDeferredResult.setResult(ResponseEntity.ok().body(Map.of("chatRoomId", result.getChatRoomId)));
}
// 콜백 시, redis와 대기 큐에서 본인과 매칭 상대 제거
deferredResult.onCompletion(() -> {
redisService.remove(key);
redisService.remove(partnerKey);
que.remove(deferredResult);
que.remove(partnerDeferredResult);
});