12. 채팅 시스템
채팅
다음과 같이 목적이 나뉜다.
1:1 채팅에 집중하는 WhatsApp, WeChat, 카카오톡 같은 개인 앱
그룹 채팅에 중점을 둔 Slack 같은 업무용 앱
대규모 그룹의 소통과 응답 지연이 낮은 디스코드 같은 음성 채팅 앱
1:1 채팅에 집중하는 경우 보통 다음과 같은 기능을 갖는다.
응답 지연이 낮은 1:1 채팅이 가능해야 한다.
수백명이 참여 가능한 그룹 채팅 기능도 제공해야 한다.
사용자의 접속 상태를 표시하는 기능이 필요하다.
다양한 단말에서 한 계정이 동시에 접근할 수 있어야 한다.
푸시 알림을 지원해야 한다.
채팅 서비스를 제공하여 클라이언트가 직접 통신하는 대신 채팅 서비스를 통해 통신하도록 해야 한다.
컴포넌트
무상태 서비스
로그인, 회원가입, 사용자 프로필 표시 등을 처리한다.
Service Discovery 기능
클라이언트가 접속할 채팅 서버의 DNS 호스트명을 클라이언트에 알려주는 기능을 제공해야 한다.
이를 통해 특정 서버에 클라이언트가 몰리지 않도록 해야 한다.
ZooKeeper 등 분산 시스템을 이용해 사용 가능한 모든 채팅 서버에 클라이언트 연결을 분산시킬 수 있다.
상태 유지 서비스
클라이언트가 채팅 서버와 독립적인 네트워크 연결을 유지해야 한다.
다음 그림과 같은 형태로 메시지가 처리된다.
사용자가 채팅 서버로 메시지를 보내면, 메시지 ID 생성기로 ID 생성 후 큐에 입력한다.
큐에 있던 메시지는 저장소에 저장된다.
사용자가 만약 채팅 서버에 접속중이라면 해당 서버로 메시지를 보내 실제 사용자 웹소켓으로 데이터가 보내지도록 한다. 접속 중이 아니라면 푸시 알림 메시지를 푸시 알림 서버에 보낸다.
연결
단순 HTTP 통신서버
HTTP 프로토콜과 keep-alive 헤더를 사용해 클라이언트와 서버의 연결을 끊지 않고 계속 유지하도록 한다. 이를 통해 TCP handshake 횟수를 줄일 수 있다.
폴링
클라이언트가 주기적으로 서버에 요청을 보내 새 메시지가 있는지 확인하는 방식이다.
폴링을 자주하면 비용이 많이 소요되고, 메시지가 한동안 없는 경우 비효율적으로 계속 요청을 보내게 된다.
롱 폴링
클라이언트는 새 메시지가 반환되거나 타임아웃 될 때 까지 연결을 유지한다.
새 메시지를 받으면 기존 연결을 종료하고 새로운 연결을 맺는다.
메시지를 보내는 클라이언트와 수신하는 클라이언트가 같은 채팅 서버에 접속하지 않을 수 있다. 동일한 채팅 서버에 접속하지 못하면, 새 메시지가 오더라도 해당 연결으로 데이터를 반환할 수 없다.
폴링과 마찬가지로 비효율적인 면이 있다.
웹소켓
서버가 클라이언트에게 비동기 메시지를 보낼 때 가장 널리 사용하는 기술이다.
처음에는 HTTP 연결이지만 특정 핸드쉐이크 절차를 거쳐 웹소켓 연결로 업그레이드된다.
서버는 비동기적으로 클라이언트에게 메시지를 전송할 수 있다.
80, 443 등 기본 포트를 사용하므로 기존 방화벽이 있더라도 문제 없다.
연결을 계속 맺고 있어야 하므로 서버에서 연결 관리를 잘 해주어야 한다.
데이터 저장소
채팅 시스템의 데이터인 채팅 이력을 어떻게 저장할 지 고민해야 한다.
보통 최근에 주고받은 메시지를 조회하지만, 검색 기능이나 언급된 부분 조회, 특정 메시지로 점프하는 경우도 지원해야 한다.
key-value 저장소를 사용하면 데이터 접근 지연시간이 낮고, 데이터에 대한 무작위 접근을 처리하기에 RDBMS보다 낫다.
1:1 채팅의 경우 메시지의 ID만 기본 키로 두면 되고, 그룹 채팅의 경우 메시지 ID와 그룹 ID를 함께 복합키로 설정해야 한다.
메세지 ID는 고유해야 하고 시간순으로 정렬하는 기준이 되어야 한다. 이 때 모든 채팅방 사이에서 고유하지는 않아도 되고, 하나의 채팅방 내에서만 메시지 ID가 고유하면 된다.
여러 단말 동기화
cur_max_message_id 변수를 통해 해당 단말에서 관측된 가장 최신 메시지의 ID를 추적한다.
아래 두 조건을 만족하는 메시지는 새로운 메시지로 간주한다. 새로운 메시지를 조회해 동기화해야 한다.
메시지 수신자 ID가 현재 로그인한 사용자 ID와 동일하다.
저장소의 메시지 ID가 cur_max_message_id보다 크다.
그룹 메시지
사용자마다 메시지 동기화 큐를 부여해, 누군가 그룹 채팅방에 새 메시지를 남기면 속해있는 모든 사람들의 메시지 동기화 큐에 메시지를 입력해주어야 한다.
새로운 메시지가 왔는지는 본인의 메시지 동기화 큐만 확인하면 된다.
그룹에 속한 인원이 많으면 같은 메시지를 모든 사용자의 큐에 복사하는 것이 비효율적이게 된다.
접속 상태 표시
클라이언트가 서버와 웹소켓 연결을 맺은 후에는 접속 상태를 활성화하고 last_active_at 타임스탬프 값을 바로 최신화한다.
연결이 끊겼다면 연결이 일시적으로 끊겼다가 다시 회복되는 것이 반복될 때 잘 대처하기 위해 일정 시간동안 끊김이 계속 유지된 것을 확인 후 사용자의 접속 상태를 비활성화해야 한다.
이를 확인하기 위해 클라이언트쪽에서 서버로 heartbeat 이벤트를 주기적으로 보내고, 서버는 이를 주기적으로 확인해 접속 상태를 확인하는 방식을 사용할 수 있다.
제3자 서비스 연동
새 메시지가 왔을 때 푸시 알림을 주어야 한다.
Last updated