소켓 프로그래밍으로 채팅 애플리케이션을 구현해보자.
그리고 Rate Limiter와 Token Bucket 방식의 API 처리율 제한기를 적용해보자.
⚡️ 채팅 애플리케이션
일단 STOMP는 텍스트 기반의 메시징 프로토콜로, 주로 메시지 브로커와 클라이언트 간의 통신을 위해 사용된다.
메시지의 송수신을 담당하며, 클라이언트가 구독한 주제에 대해 응답을 받을 수 있도록 해준다.
웹 애플리케이션에서 채팅, 실시간 업데이트, 알림 등을 구현할 때 많이 사용된다.
해당 그림을 살펴보면 발행-구독 매커니즘으로 특정 주제에 메시지를 보내면, 해당 주제를 구독하고 있는 subscriber 모두에게 메시지가 전달되는 구조다.
💫 애플리케이션 구조
해당 채팅 애플리케이션은 크게 두 가지의 Controller가 존재한다.
하나는 채팅방 목록 조회, 채팅방 생성, 특정 채팅방 등을 조회하는 채팅방 API를 관리하는 ChatRoomController다.
다른 하나는 채팅 메시지의 송수신을 관리하는 ChatController다.
차례대로 하나씩 도식도를 통해 살펴보자.
해당 도식도는 채팅방 API와 관련된 요청-응답 사이클 구조다.
1. Client가 ChatRoomController로 요청을 보낸다. 이 요청은 채팅방 생성, 채팅방 목록 조회, 특정 채팅방 조회 등이 될 수 있다.
2. 채팅방 컨트롤러는 요청에 따라 적절한 메서드를 호출하고, 필요한 경우 채팅방 저장소에서 데이터를 조회한다.
3. ChatRoomRepository는 DB나 메모리 등에서 생성하거나 조회한 채팅방 데이터를 가져옵니다.
4. ChatRoomController는 가져온 데이터를 클라이언트한테 반환한다. 이때 Vue.js나 Thymeleaf 등의 뷰 템플릿 엔진을 사용하여 데이터를 HTML로 변환한다.
5. 마지막으로, 반환된 HTML 뷰를 클라이언트한테 응답으로 보낸다.
1. Client가 ChatController에 메시지 내용을 담아서 전송 요청을 한다.
2. ChatController에서 메시지는 처리율 제한기 로직을 거치고 SimpMessageSendingOperations에 넘긴다.
3. 현재 채팅방을 구독한 클라이언트들(Client2, Client3, Client4)한테만 메시지를 전송한다.
해당 순서도는 ChatController에서 실행되는 처리율 제한기 로직이다.
Client가 메시지를 전송하고 처리율 제한에 걸리지 않으면 해당 채팅방에 구독한 Client들한테 메시지가 전송된다.
하지만 처리율 제한에 걸리면 해당 메시지는 아무에게도 전송되지 않는다.
자세한 코드는 아래 링크를 참고하면 된다.
GitHub - pdohyung/spring-chat-server
Contribute to pdohyung/spring-chat-server development by creating an account on GitHub.
github.com
해당 채팅 애플리케이션은 아래 블로그를 참고하여 구현했다.
Stomp를 활용한 실시간 채팅 프로그램 구현: 웹소켓 최적화 및 효율적인 메시징 전송 (4)
이번에는 Websocket의 프로세스를 좀더 고도화 하고 메시징에 좀 더 최적화된 방식을 구현하기 위해 Stomp를 적용해 보겠습니다. Stomp란? stomp는 메시징 전송을 효율적으로 하기 위해 나온 프로토콜
rhgustmfrh.tistory.com
💫 실행 영상
영상에서 채팅 메시지 도배를 하는 경우, 서버에서는 처리율 제한 장치로 아래 로그와 API를 제한해서 메시지가 전송되지 않는다.
현재 적용된 방식은 Token Bucket 방식으로 초기에 정해진 토큰 개수만큼만 메시지를 전송하고,설정한 일정 시간마다 새로운 토큰이 생성된다.
아래 사진에서 접근 세션 ID가 p2phvui0인 클라이언트는 영상에서 두 번째로 접속한 익명1 사용자를 가리킨다.
💫 처리율 제한 장치 살펴보기
각각의 처리율 제한 장치를 살펴보자.
첫 번째로 실행 영상에서 적용된 Token Bucket을 확인해보자.
본인은 Bucket4j 라이브러리를 사용했다.
Token Bucket 알고리즘은 설정한 특정 시간 동안 일정량의 토큰을 생성하여 사용하도록 처리율을 제한한다.
BucketService 코드를 보면 Client는 최초에 resolveBucket() 메서드를 통해 Bucket를 생성한다.
해당 메서드 내부에서 사용되는 newBucket() 메서드를 보면, 최초 Bucket 크기는 5이고 1초마다 1개의 새로운 토큰이 생성된다.
즉, 5개의 토큰을 모두 사용하고 1초에 1번 이상의 채팅 메시지를 보내면 채팅 메시지는 처리율 제한에 걸리게 된다.
위 ChatController는 Token Bucket을 사용하는 로직이다.
다음은 Rate Limiter를 살펴보자.
해당 처리율 제한은 Resilience4j 라이브러리를 사용했다.
Rate Limiter는 쉽게 설명하면 주어진 시간동안 처리할 수 있는 요청의 수를 제한한다.
Rate Limiter가 적용된 ChatController 코드다.
CustomRateLimiterConfig 코드를 보면 1초에 1개의 요청만 가능하고 타임아웃이 0이므로 즉시 처리율이 제한된다.
말 그대로 1초에 1개의 요청만 가능하다는 것이다.
위 ChatController는 Rate Limiter를 사용하는 로직이다.
두 가지 처리율 제한기 ChatController는 공통적으로 Client 최초로 입장하는 경우에는 입장 메시지를 전송한다.
⚡️ 결론
채팅 애플리케이션을 구현하고 두 가지의 처리율 제한기를 직접 적용해봤다.
발행-구독 기반의 채팅 소켓 프로그래밍을 이해할 수 있었고, 처리율 제한기가 어떤 로직으로 동작하는지 확인할 수 있었다.
내가 실생활에서 채팅 애플리케이션을 사용했을 때는 처음에는 채팅 메시지를 여러 개 보낼 수 있지만 특정 개수를 보내고 나서는 처리율이 제한되는 것을 체감했다.
그러므로 Rate Limiter 방식보다는 일정 토큰 개수를 생성하고 모두 소진했을 때, 처리율을 제한하는 Token Bucket 방식이 많이 채택되고 있다는 것을 느꼈다.
'Spring' 카테고리의 다른 글
Redis Sorted Sets으로 인기 검색어 구현하기 (0) | 2024.04.09 |
---|---|
좋아요 조회 쿼리 N + 1 문제 해결하기 (0) | 2024.04.05 |
[Spring] could not initialize proxy [...] - no Session 오류 (0) | 2024.04.02 |
QueryDSL 동적 정렬 쿼리 OrderSpecifier 구현하기 (0) | 2024.03.28 |
Spring Security 없이 소셜 로그인 구현하기 (0) | 2024.03.17 |