Redis와 WebSocket 기반 채팅 목록 리프레시 및 알림 시스템
새 메시지 도착 시 채팅 목록을 실시간으로 리프레시하고 알림을 표시하는 시스템을 Redis와 WebSocket으로 구현.
인스타그램 DM처럼 유저가 채팅 서비스에 연결되어있고 새로운 채팅을 받았을 때 유저의 화면을 어떻게 업데이트 해줘야할까 고민해봤다
구현은 성공했으나 우여곡절이 많았다. 채팅서비스가 이렇게 고려해야할 사항이 많은줄 몰랐다..
그래도 원하는데로 구현이 되어서 기쁘다!
-
- 새 메시지 도착 시 현재 연결된 유저에게 신호를 보내 채팅 목록을 리프레시하고, 새 메시지를 빨간색 마크로 표시.
- Redis로 DB 부하를 줄이고, WebSocket으로 실시간 업데이트 제공.
목표
- 구독 경로:
-
- 클라이언트가
/topic/toggle-refresh/
구독.
stompClient.subscribe(`/topic/toggle-refresh/${userId}`
- 클라이언트가
- 역할:
-
- 특정 유저(
userId
)에게 새 메시지 도착 시 리프레시 신호 전달.
- 특정 유저(
WebSocket 구독 설정
- 메시지 저장 전:
-
MessageDto
에서chatRoomId
추출.- Redis에서 해당
chatRoomId
의 유저 목록 조회 (키:"chatroom_users:
)." - Redis에 없으면 MySQL에서 유저 목록 가져와 캐싱 (
chatRoomUserRepository.findUserIdsByChatRoomId
).
@Transactional public void syncMessagesByChatRoomId(Long chatRoomId) { List
pendingMessageDTOs = redisService.getPendingMessages(chatRoomId); List pendingMessages = pendingMessageDTOs.stream() .map(dto -> { Message message = new Message(); message.setChatRoom(chatRoomService.getChatRoomById(dto.getChatRoomId())); // Handle system messages (senderId = 0 or null) if (dto.getSenderId() == null || dto.getSenderId() == 0) { message.setUser(null); // System message } else { message.setUser(userService.getUserById(dto.getSenderId())); } message.setContent(dto.getContent()); message.setEnrolledAt(dto.getEnrolledAt()); return message; }) .toList(); if (!pendingMessages.isEmpty()) { List savedMessages = messageRepository.saveAll(pendingMessages); redisService.removePendingMessage(chatRoomId); log.info("채팅방 {} 싱크완료", chatRoomId); } else { log.info("채팅방 {} 싱크할 메세지 없음", chatRoomId); } } - 효과:
-
- Redis를 통해 채팅방 유저 조회로 MySQL 부하 감소.
메시지 전송 및 Redis 처리
- 리프레시 신호:
-
- 메시지 저장 후 WebSocket으로
/topic/toggle-refresh/
에 신호 전송.
- 메시지 저장 후 WebSocket으로
- 클라이언트 반응:
-
- 구독 중인 유저의 채팅 목록이 리프레시되고, 새 메시지에 빨간색 마크 표시.
리프레시 요청 및 알림 표시
- 유저 구독:
-
- 클라이언트가
/topic/toggle-refresh/
구독 → 알림 대기.
- 클라이언트가
- 메시지 전송:
-
- 클라이언트가 메시지 전송 →
MessageDto
에서chatRoomId
추출. - Redis에서
"chatroom_users:
조회 → 없으면 DB에서 캐싱." - Redis에 메시지 저장 (키:
"message_queue:
)."
- 클라이언트가 메시지 전송 →
- 알림 전달:
-
chatRoomId
의 각userId
에 대해/topic/toggle-refresh/
로 신호 전송.- 연결된 유저의 채팅 목록 리프레시 → 새 메시지 표시.
동작 방식
- Redis 키 구조:
-
"user_chatrooms:
: 유저가 속한" chatRoomId
목록 (Redis Set)."chatroom_users:
: 채팅방에 속한" userId
목록 (Redis Set)."message_queue:
: 미처리 메시지 저장 (Redis List)."
- 캐싱 로직:
-
- Redis에
"chatroom_users:
없으면 DB에서 조회 후 캐싱 (" SADD
). "user_chatrooms:
도 동일: DB에서" chatRoomIds
가져와 캐싱.
- Redis에
- WebSocket 메시지:
-
/topic/toggle-refresh/
로 간단한 신호 전송 (예:{ "chatRoomId": 123, "newMessage": true }
).
- 클라이언트 UI:
-
- 리프레시 후 새 메시지 항목에 빨간색 마크 추가.
디테일한 흐름
- 문제:
-
- 신규 채팅방 생성 시 Redis에 등록되지 않아
getChatRoomIdsByUserId
와getUserIdsByChatRoomId
에서 누락.
- 신규 채팅방 생성 시 Redis에 등록되지 않아
- 원인:
-
addChatRoomIdsAndUserIds
가 생성 시 호출되지 않음.
- 해결:
-
- 채팅방 생성 시 각 유저에 대해
addChatRoomIdsAndUserIds(userId, chatRoomId)
호출. - Redis에
"user_chatrooms:
와" "chatroom_users:
즉시 업데이트."
- 채팅방 생성 시 각 유저에 대해
- 결과:
-
- 신규 채팅방이 Redis에 등록되고, 관련 키에서 정상 조회됨.
문제 및 해결: 신규 채팅방 등록
- 문제 해결:
-
- 신규 채팅방 포함 실시간 알림, MySQL 부하 감소.
- Redis:
-
"chatroom_users:
와" "user_chatrooms:
로 유저-채팅방 관계 캐싱, 메시지 큐로 성능 최적화."
- WebSocket:
-
/topic/toggle-refresh/
로 실시간 신호 전달.
- 결과:
-
- 모든 채팅방(기존+신규)에 대해 즉각적인 알림과 UI 업데이트 제공.
요약
Posted on 2025.03.01