2025-12-03
오늘 배운 것
- 실시간 메시징 어떻게 구현
- 개념
- 서버와 클라이언트가 양방향으로 즉시 데이터 교환이 가능한 시스템
- 메시지를 보내면 거의 실시간에 가깝게 상대방에게 전달되는 구조
-
기본 구조
[ 클라이언트 ] ↓ ↑ (1) 실시간 통신 계층 (WebSocket 등) ↓ ↑ (2) 메시징 서버 ↓ ↑ (3) 메시지 브로커 (Pub/Sub) ↓ ↑ (4) 저장소(DB, 캐시) - 실시간 통신 방식
- WebSocket
- TCP 기반 양방향 통신
- 클라이언트 ↔ 서버 둘 다 자유롭게 메시지 보냄
- 끊기지 않는 장기 연결
- 실시간 채팅 기본
- SSE(Server-Sent Events)
- 서버 → 클라이언트 단방향 스트림
- 실시간 알림(주가, 뉴스)에 주로 사용
- Long Polling
- HTTP 요청을 오래 유지해서 서버가 응답 가능할 때까지 기다리는 방식
- WebSocket이 불가능한 환경의 fallback
- fallback : 원래 방식이 안될 때 대체로 사용하는 방식
- WebSocket
- 메시징 서버
- 클라이언트와 WebSocket을 직접 연결하는 서버
- 역할
- WebSocket 핸드 셰이크 처리
- 유저 인증(JWT 등)
- 연결 관리 (userId ↔ connectionId)
- 메시지 수신 및 검증
- 메시지를 브로커로 publish (보내기)
- 브로커로부터 메시지를 subscribe (받기)
- 다시 클라이언트로 push
- 특징
- Stateless(무상태)로 설계해야 확장 쉬움 → 상태는 모두 Redis나 브로커로 위임
- 메시지 브로커 / Pub-Sub 시스템
- Pub/Sub (Publish-Subscribe)
- Producer가 토픽에 메시지를 publish하면, 그 토픽을 subscribe한 Consumer들에게 메시지가 전달되는 구조
- 대표 브로커 기술
- Redis Pub/Sub
- 가벼움, 빠름
- 순서보장이 약함
- 메시지 유실 가능
- 소규모 채팅/알림 사용
- Redis Streams
- 소비 그룹, offset 관리됨
- 메시지 유실 방지
- Kafka와 유사한 구조의 경량 버전
- Kafka
- 파티션 단위로 강력한 순서 보장
- 메시지 유실 방지
- 대규모 서비스에서 가장 많이 사용
- RabbitMQ
- 라우팅, 지연 메시지 등 기능 풍부
- 실시간 메시지보다 업무 로직 큐에 자주 사용
- Redis Pub/Sub
- RabbitMQ vs Kafka
- RabbitMQ
- 메시지 큐 기반
- 라우팅, 지연메시지, 우선순위 등 기능 풍부
- 메시지를 업무 로직 처리용으로 보내는데 강함
- 실시간 채팅용으로는 덜 적함(순서보장 약함)
- 예 : 주문 처리 큐, 이메일 전송 큐, 백그라운드 작업
- Kafka
- 분산로그 + Pub/Sub 기반
- 압도적인 처리량
- 파티션 단위 순서보장 강함
- 메시지를 장기간 보관 가능
- 실시간 스트림 처리/채팅에 최적
- 예 : 채팅 메시징, 실시간 로그 수집, 대규모 이벤트 스트리밍, MSA 이벤트 브로커
- RabbitMQ
- Pub/Sub (Publish-Subscribe)
- 방 개념과 라우팅
- 실시간 메시징의 90%는 방 단위 라우팅
- 예
- room-123에 메시지가 발행되면 → room-123에 있는 유저들만 메시지 받음
- 구현
- 유저가 로그인하면 참여 방 목록 저장
- 메시지 publish 시 roomId 같이 전달
- 브로커가 roomId에 필요한 서버 인스턴스들에게 전달
- 해당 서버가 클라이언트에게 push
- 확장성 문제와 해결 방식
- 실시간 메시징은 서버 1대로는 절대 못 함
- 해결방식
- 메시징 서버 수평 확장(스케일 아웃)
- 여러 서버가 웹소켓 연결을 분담
- 상태는 레디스 또는 브로커에 저장해 공유
- 방 단위 샤딩
- 룸 아이디를 기반으로 shard = roomId % 서버 개수
- 이런 식으로 특정 서버가 특정 방을 담당하게 할 수도 있음
- 브로커 기반 확장
- 카프카 파티션 개수를 늘리면 메시지 처리량을 늘릴 수 있음
- 카프카 파티션 : 토픽을 여러 개의 조각으로 나눈 것
- 각 파티션은 독립적으로 메시지를 순서대로 저장
- 파티션 개수가 많을수록 병렬 처리량 증가
- 카프카 파티션 : 토픽을 여러 개의 조각으로 나눈 것
- 카프카 파티션 개수를 늘리면 메시지 처리량을 늘릴 수 있음
- 메시징 서버 수평 확장(스케일 아웃)
- 메시지 순서 보장
- 순서 보장이 필요한 이유
- 같은 채팅방에서 메시지 순서 뒤죽박죽이면 사용자 경험 망가짐
- 대표 방식
- 카프카 파티션 기반 순서 보장
- 방단위로 하나의 파티션만 사용 → FIFO
- 샤딩 기반으로 한 서버가 해당 방 메시지 모두 처리하게 하는 방식
- 카프카 파티션 기반 순서 보장
- 순서 보장이 필요한 이유
- 메시지 중복 처리
- 메시지를 재전송하면 중복이 발생할 수 있음
- 메시지마다 유니크 아이디 부여
- 클라이언트는 이미 처리한 메시지 아이디면 무시
- 유실방지
- 대표 전달 방식
- at-most-once
- 중복 없음
- 유실 가능
- at-least-once
- 재전송 포함
- 중복 발생 가능 → 멱등성 필요
- exactly-once
- 실질적으로 구현하기 어려움
- 큰 시스템은 대부분 at-least-once 기반
- at-most-once
- 대표 전달 방식
- 오프라인 메시지 처리
- 유저가 오프라인일 경우
- 메시지를 DB에 저장
- 모바일 Push(FCM/APNS) 전송
- FCM(Firebase Cloud Messaging)
- 구글이 제공하는 모바일 푸시 알림 서비스
- 안드로이드 기본
- iOS도 지원하지만 APNS를 내부적으로 사용
- 오프라인인 사용자를 깨울 때 사용
- APNS(Apple Push Notification Service)
- 애플이 제공하는 iOS Push 알림 서비스
- iPhone은 FCM을 직접 쓰는게 아니라 FCM → 내부적으로 APNS로 메시지를 다시 보내서 전달
- FCM(Firebase Cloud Messaging)
- 유저 재접속 시 → 마지막 읽은 메시지 아이디 이후 메시지 재전송
- 유저가 오프라인일 경우
- Presence & 상태 동기화 (온라인/오프라인 표시)
- Redis에 userId → online/offline + serverId 저장
- 상태 변경 시 브로커로 이벤트 발행
- 친구 목록/방 유저 목록 업데이트
- 저장소
- 메시지 로그 저장
- RDB : MySQL, PostgreSQL
- NoSQL: Cassandra, DynamoDB
- 검색: ElasticSearch
- 읽음 처리, 최근 N개 로딩 최적화 등을 위해 레디스 캐시 사용
- 메시지 로그 저장
- 장애 복구
- 서버 장애 발생 시
- 웹소켓 끊김 → 클라이언트 자동 재연결
- 재연결 시 미처 못받은 메시지를 DB에서 fetch
- Stateless 서버라서 다른 인스턴스가 연결 처리 가능
- 서버 장애 발생 시
- 인증/보안
- 웹소켓 업그레이드 시 JWT 검사
- TLS 사용
- 방 참여 권한 체크(ACL)
- ACL(Access Control List) : 누가 어떤 리소스에 접근 가능한지 정의한 권한 목록
- Rate Limit 적용(스팸 방지)
- 너무 많은 요청을 보내는 클라이언트 제한하는 기술
- 예
- 초당 메시지 전송 개수 제한
- IP당 초당 요청 개수 제한
- API 호출 제한
- 방법
- 토큰 버킷 알고리즘
- leaky bucket
- Redis 기반 rate limiting
- 운영/모니터링
- 프로메테우스 : 연결 수, 초당 메시지 수, 지연(단락) 측정
- 그라파나 : 시각화
- 로그: ELK Stack
- ELK (Elasticsearch Logstash Kibana)
- 로그 수집/저장/검색/시각화 통합 도구 세트
- ELK (Elasticsearch Logstash Kibana)
- 알림: Slack/Discord/Webhook
- 개념