웹 내에서 방에서 게임에 초대하기 위한 현재 접속자를 구현하게 되었다. (구현해본적 없는 기능이니... 정답에 멀어진 방법일 수 있다.)
문제점은 웹 브라우저 종료 방식에는 여러 방법이 있다는 것. 즉 접속자 데이터를 추가하는 것에는 제한이 없지만, 접속이 끝난 사용자에 대한 처리는 애매하다는 것...
프로젝트가 복잡해지지 않으려면 최대한 지금 가지고 있는, 이미 종속성 추가된 기능 내에서 개발해야 한다. 현재 접속여부 구현에 필요한 자원은 Redis, Scheduler, STOMP 정도로 생각했다.
접속자가 "나 접속해 있어요" 서버에 알려주면 되는 것 이라는 생각에 그럼 서버에서 접속 request를 보내주는 유저에 대해서 정보를 관리하면 되겠다 싶었다.
구상한 접속자 확인 프로세스는 위 사진과 같다. 스케쥴러를 통해서 소켓 구독자들에게 n초에 한 번 접속여부를 묻는다. 답변이 돌아오면 해당 유저의 번호를 Redis에서 관리 시킨다.
Redis에서는 Set을 이용해서 유저의 번호를 관리하고 매 일정 주기마다 Set을 초기화해서 실시간 데이터로 데이터를 제공할 수 있도록 한다.
이렇게 구현 했을 때 생각지 못한 트러블에 마주쳤다. 로컬에서는 거의 즉각적인 응답으로 확인 하지 못했었다. 배포 서버에서 점검 해보니 클라이언트들의 응답 속도는 각자 달랐고, Set이 초기화 된 직후 아직 접속 요청을 받지 못한 시점에서 접속자 여부 데이터를 제공하면 신뢰성 없는 데이터가 나가게 되었다.
두 개의 셋으로 접속자를 관리해서 응답 시간차를 극복해 보자!
@Scheduled(fixedRate = 20000)
public void userActiveManage() {
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
// 1. 접속자 자료구조 2개 체킹
Set<Long> activeBackupUsers = ops.get("activeBackupUsers") != null ? (Set<Long>) ops.get("activeBackupUsers") : new HashSet<>();
Set<Long> activeUsersTemp = new HashSet<>();
ops.set("activeUsers", activeBackupUsers);
ops.set("activeBackupUsers", activeUsersTemp);
}
@Scheduled(fixedRate = 3000) // 3초 마다
public void sendActiveDataSchedule() {
messagingTemplate.convertAndSend("/active", "ping");
}
public void addActiveUser(Long memeberId) {
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
Set<Long> activeUsers = (Set<Long>) ops.get("activeUsers");
Set<Long> activeBackupUsers = (Set<Long>) ops.get("activeBackupUsers");
activeUsers.add(memeberId);
activeBackupUsers.add(memeberId);
System.out.println("접속중 : " + activeUsers);
ops.set("activeUsers", activeUsers);
ops.set("activeBackupUsers", activeBackupUsers);
}
이제 접속자 정보를 제공할때 contains를 통해 boolean으로 값을 조회 할 수 있게 되었다!