티스토리 뷰
🔗 Redis PUB/SUB, WebSocket 실시간 채팅 개발기 1 : 설계하기
🔗 Redis PUB/SUB, WebSocket 실시간 채팅 개발기 2 : 채팅 기능 구현
🔗 Redis PUB/SUB, WebSocket 실시간 채팅 개발기 4 : 채팅방 리스트 최신화 성능 개선기
안녕하세요!
저번 글에서는 채팅 기능 코드를 작성하였습니다.
채팅 메시지를 웹소켓과 Redis를 활용해서 구현하는 것은 이해를 했는데, 채팅 기능에서는 채팅방 리스트 또한 중요합니다.
카카오톡을 보면 마지막 메시지에 따라 채팅방이 정렬이 됩니다.
그렇다면 어떻게 채팅방 리스트의 최신화를 해야 할까요?
🌟 채팅방 리스트의 최신화?
사실 이 문제 때문에 저희는 몇 주를 고민했었습니다.
채팅에 대한 정보는 구글에 널리 퍼져있었지만, 채팅방 리스트에 대한 정보는 찾기 어려웠습니다.
그렇기 때문에 이 기능만큼은 스스로 고민해 보며 정답을 찾고자 했습니다.
✏️ Before
원래는 채팅방 리스트를 Polling 방식을 사용해 30초 간격으로 호출하려고 했습니다.
Polling 방식이란 이전 글에서도 정리했듯이, 클라이언트가 서버에 반복적으로 요청하는 방식입니다.
하지만 이 방법은 다음과 같은 문제점을 찾았습니다.
1) 채팅방 마지막 메시지의 시간과 채팅방 리스트의 최신화 시간의 시간차가 발생합니다.
2) 최신 메시지가 없을 경우에도 서버에 요청하여, 불필요한 네트워크 트래픽이 발생할 수 있습니다.
🌟 After
그래서 저희는 Redis pub/sub 와 Websocket을 활용하여 채팅방 리스트를 같이 보내주기로 하였습니다.
솔직히 코드로 구현할 때 과연 될까? 두근거렸는데 진짜 구현돼서 놀랐던 경험이 있습니다 ㅎㅎ
채팅할 때마다, 최신화된 채팅방 리스트도 같이 발행해 주는 것입니다.
✏️ 코드
1. RedisConfig
이전 글에서 작성한 RedisConfig에 sendRoomList 메서드를 추가해 주었습니다.
메시지가 pub 될 때마다, sendRoomList 메서드도 같이 호출됩니다.
@RequiredArgsConstructor
@Configuration
public class RedisConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerRoomList (
MessageListenerAdapter listenerAdapterChatRoomList,
ChannelTopic channelTopic
) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory());
container.addMessageListener(listenerAdapterChatRoomList, channelTopic);
return container;
}
/** 실제 메시지 방을 처리하는 subscriber 설정 추가*/
@Bean
public MessageListenerAdapter listenerAdapterChatRoomList(RedisSubscriber subscriber) {
return new MessageListenerAdapter(subscriber, "sendRoomList");
}
}
2. MessageSubDto
발행될 메시지를 묶어줄 Dto를 만들어주었습니다.
보따리 개념으로 생각하시면 편합니다.
한 번에 묶어서 pub 할 때, 하나씩 꺼내서 발행해 주는 방식입니다.
ChatRoomListGetResponse : 사용자 닉네임, 상대방 닉네임, 상품 이름, 상품 이미지 등 채팅방 내부의 필요한 값들이 담겼습니다.
@Getter
@Setter
@Builder
public class MessageSubDto {
private Long userId;
private Long partnerId;
private ChatMessageDto chatMessageDto;
private List<ChatRoomListGetResponse> list;
private List<ChatRoomListGetResponse> partnerList;
}
3. ChatService
채팅방의 정보를 보내주기 위해서 작성한 코드입니다.
채팅방 리스트들을 각 채팅방마다 저장된 마지막 메시지들의 시간을 기준으로 내림차순 하여 정렬한 후, messageSubDto에 담아 발행합니다.
@RequiredArgsConstructor
@Service
@Slf4j
public class ChatService {
private final RedisPublisher redisPublisher;
private final ChatRoomRedisRepository chatRoomRedisRepository;
private final ChatRoomService chatRoomService;
/**
* 채팅방에 메시지 발송
*/
public void sendChatMessage(ChatMessageDto chatMessage, String accessToken) {
// 0. redis에 해당 채팅방roomId(key)에 마지막 메세지(value)를 넣어준다.
chatRoomRedisRepository.setLastChatMessage(chatMessage.getRoomId(), chatMessage);
Long userId = chatMessage.getUserId();
Long partnerId;
// 1. 채팅방이 삭제되는 것이라면 delete 를 해준다.
if (chatMessage.getType().equals(MessageType.DELETE)) {
chatRoomService.deleteChatRoom(accessToken, chatMessage.getRoomId(), userId);
chatRoomRedisRepository.deleteChatRoom(userId,chatMessage.getRoomId());
}
ChatRoomListGetResponse newChatRoomList = null;
if (chatRoomRedisRepository.existChatRoom(userId, chatMessage.getRoomId())) {
newChatRoomList = chatRoomRedisRepository.getChatRoom(userId, chatMessage.getRoomId());
} else {
newChatRoomList = chatRoomService.getChatRoomInfo(accessToken, chatMessage.getRoomId());
}
partnerId = getPartnerId(chatMessage, newChatRoomList);
// 2. 채팅방 리스트에 새로운 채팅방 정보가 없다면, 넣어준다. 마지막 메시지도 같이 담는다. 상대방 레디스에도 업데이트 해준다.
setNewChatRoomInfo(chatMessage, newChatRoomList);
// 3. 마지막 메시지들이 담긴 채팅방 리스트들을 가져온다.
List<ChatRoomListGetResponse> chatRoomListGetResponseList = chatRoomService.getChatRoomList(userId, accessToken);
// 4. 파트너 채팅방 리스트도 가져온다. (파트너는 userId 로만)
List<ChatRoomListGetResponse> partnerChatRoomGetResponseList = getChatRoomListByUserId(partnerId);
// 5. 마지막 메세지 기준으로 정렬 채팅방 리스트 정렬
chatRoomListGetResponseList = chatRoomService.sortChatRoomListLatest(chatRoomListGetResponseList);
partnerChatRoomGetResponseList = chatRoomService.sortChatRoomListLatest(partnerChatRoomGetResponseList);
MessageSubDto messageSubDto = MessageSubDto.builder()
.userId(userId)
.partnerId(partnerId)
.chatMessageDto(chatMessage)
.list(chatRoomListGetResponseList)
.partnerList(partnerChatRoomGetResponseList)
.build();
redisPublisher.publish(messageSubDto);
}
}
4. RedisSubcriber
채팅방 리스트 sub 주소 ("/sub/chat/roomlist/{userId}")에 채팅을 하고 있는 두 명의 사용자의 객체를 발행하면 됩니다.
@Slf4j
@RequiredArgsConstructor
@Service
public class RedisSubscriber {
private final ObjectMapper objectMapper;
private final SimpMessageSendingOperations messagingTemplate;
public void sendMessage(String publishMessage) {
try {
ChatMessageDto chatMessage =
objectMapper.readValue(publishMessage, MessageSubDto.class).getChatMessageDto();
// 채팅방을 구독한 클라이언트에게 메시지 발송
messagingTemplate.convertAndSend(
"/sub/chat/room/" + chatMessage.getRoomId(), chatMessage
);
} catch (Exception e) {
log.error("Exception {}", e);
}
}
public void sendRoomList(String publishMessage) {
try {
MessageSubDto dto = objectMapper.readValue(publishMessage, MessageSubDto.class);
List<ChatRoomListGetResponse> chatRoomListGetResponseList = dto.getList();
List<ChatRoomListGetResponse> chatRoomListGetResponseListPartner = dto.getPartnerList();
Long userId = dto.getUserId();
Long partnerId = dto.getPartnerId();
// 로그인 유저 채팅방 리스트 최신화 -> 내 계정에 보냄
messagingTemplate.convertAndSend(
"/sub/chat/roomlist/" + userId, chatRoomListGetResponseList
);
// 파트너 계정에도 리스트 최신화 보냄.
messagingTemplate.convertAndSend(
"/sub/chat/roomlist/" + partnerId, chatRoomListGetResponseListPartner
);
} catch (Exception e) {
log.error("Exception {}", e);
}
}
}
Redis의 pub/sub를 응용하여 채팅방 리스트 최신화를 진행해 보았습니다.
다음 글에서는 어떻게 채팅방 리스트의 시간을 1500% 향상하였는지 작성하겠습니다.
'Spring boot' 카테고리의 다른 글
Redis PUB/SUB, WebSocket 실시간 채팅 개발기 4 : 채팅방 리스트 최신화 성능 개선기 (1) | 2024.02.15 |
---|---|
Redis PUB/SUB, WebSocket 실시간 채팅 개발기 2 : 채팅 기능 구현 (2) | 2024.02.15 |
Redis PUB/SUB, WebSocket 실시간 채팅 개발기 1 : 설계하기 (2) | 2024.01.31 |
[Spring boot] [redis] redisTemplate, ValueOperations에 커스텀 객체 List 형태로 저장하는 방법 - 삽질 기록 (0) | 2024.01.26 |
[Spring boot] EC2 배포시 spring boot Timezone 한국 시간으로 변경하기 (0) | 2024.01.18 |
- Total
- Today
- Yesterday
- 스터디후기
- qjzl
- 백엔드
- 야놀자X패스트캠퍼스부트캠프
- 자료구조 #스택 #큐 #덱 #선형자료구조
- 자료구조
- 카카오API
- 데이터베이스
- 국비지원캠프
- 국비지원
- 그룹스터디워크샵
- 백준
- 과정중간회고
- 야놀자
- TiL
- 백엔드부트캠프
- #국비지원취업
- 부트캠프
- springboot
- 커리어멘토링
- 그룹스터디
- be
- 패스트캠퍼스강의
- 국비지원취업
- 채팅기능개발
- 백엔드개발자
- Java
- 프로젝트후기
- boj
- 패스트캠퍼스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |