WebSocket + Redis 기반 실시간 채팅 시스템 구조 설계

shjk·2025년 4월 9일

1. 개요

실시간 채팅 시스템은 메시지 전송의 지연 시간(latency)을 최소화하고, 확장성(scalability)을 확보하며, 메시지 신뢰성(reliability)을 보장하는 것이 핵심이다. 이를 위해 WebSocket과 Redis를 조합하여 다음과 같은 구조를 설계할 수 있다:

  • WebSocket: 클라이언트-서버 간 실시간 연결 유지
  • Redis:
    • Pub/Sub: 다중 서버 간 메시지 브로드캐스트
    • Stream / List / Sorted Set: 메시지 저장 및 전달
    • Set: 실시간 접속 사용자 관리

2. 아키텍처 다이어그램

[Client] ─ WebSocket ─▶ [WebSocket Server]
                          │
                          ├─ Redis Pub/Sub (chatroom:{id})
                          │
                          └─ Redis Storage (message queue, user status)

3. 채팅 구조 핵심 요소

3.1. 채팅방(Room) 모델링

  • 고정된 ID 또는 UUID를 사용하여 각 채팅방을 구분
  • 각 채팅방은 Redis의 채널명과 1:1 매칭
채널명: chatroom:{roomID}
메시지 저장: chatroom:{roomID}:messages (List 또는 Stream)
접속 유저: chatroom:{roomID}:users (Set)

3.2. 메시지 구조

{
  "room_id": "abc123",
  "sender_id": "user42",
  "message": "안녕하세요",
  "timestamp": 1712210000
}

메시지는 JSON 직렬화되어 Redis에 저장되고, WebSocket을 통해 전달됨.


3.3. 메시지 전송 흐름

  1. 클라이언트가 WebSocket으로 메시지 전송

  2. 서버에서 메시지 파싱 후 Redis에 발행

payload := ChatMessage{
    RoomID: "abc123",
    SenderID: "user42",
    Message: "안녕하세요",
    Timestamp: time.Now().Unix(),
}

data, _ := json.Marshal(payload)
redisClient.Publish(ctx, "chatroom:"+payload.RoomID, data)
redisClient.RPush(ctx, "chatroom:"+payload.RoomID+":messages", data)
  1. Redis가 Pub/Sub을 통해 모든 서버에 메시지 브로드캐스트

  2. 서버는 해당 채팅방의 연결된 클라이언트들에게 메시지를 전송

go func() {
    sub := redisClient.Subscribe(ctx, "chatroom:"+roomID)
    for msg := range sub.Channel() {
        broadcastToClientsInRoom(roomID, msg.Payload)
    }
}()

4. 접속자 추적 및 입장/퇴장 처리

유저가 채팅방에 입장할 경우

redisClient.SAdd(ctx, "chatroom:"+roomID+":users", userID)

퇴장 시

redisClient.SRem(ctx, "chatroom:"+roomID+":users", userID)

현재 접속자 수 조회

redisClient.SMembers(ctx, "chatroom:"+roomID+":users")

5. Redis 데이터 구조 정리

타입설명
chatroom:{id}Pub/Sub 채널메시지 브로드캐스트
chatroom:{id}:messagesList / Stream메시지 기록 저장
chatroom:{id}:usersSet접속 중인 사용자 ID 목록

6. 실시간 브로드캐스트 방식

모든 서버는 각 채팅방에 대한 Redis Pub/Sub 채널을 구독하고 있으며, 하나의 서버에서 보낸 메시지가 전체 서버에 전달된다. 이를 통해 여러 서버에 분산된 클라이언트에게 동일한 메시지를 전달할 수 있다.


7. 메시지 저장과 신뢰성 보장

메시지 저장 방식

  • List는 간단하고 빠르나, 메시지 ID가 없음
  • Stream은 ID 기반 메시지 보존과 소비가 가능하므로 고급 기능이 필요할 경우 적합
redisClient.XAdd(&redis.XAddArgs{
    Stream: "chatroom:" + roomID + ":stream",
    Values: map[string]interface{}{
        "sender": userID,
        "message": content,
        "timestamp": time.Now().Unix(),
    },
})

8. 확장 고려사항

항목설명
수평 확장모든 WebSocket 서버는 동일한 Redis에 연결되어 있고, Pub/Sub으로 메시지를 동기화
멀티 디바이스 처리하나의 사용자에 대해 여러 WebSocket 연결 관리 필요 (예: Web + Mobile)
접속 유지주기적인 Ping 메시지 및 연결 상태 모니터링
메시지 영속성Redis에만 의존하지 않고, RDB 또는 MongoDB 등으로 백업 가능

9. 수식 모델링

메시지 전송

클라이언트 CiC_i가 서버 SjS_j로 메시지 mm을 전송하면,

SjmRedis PubSub (채널 Rk)broadcast{Ci1,...,Cin}S_j \xrightarrow{m} \text{Redis PubSub (채널 } R_k \text{)} \xrightarrow{broadcast} \{ C_{i_1}, ..., C_{i_n} \}

사용자 상태

접속 중인 사용자의 집합은 다음과 같이 표현된다.

Uonlineroomk={u1,u2,,un}UU_{online}^{room_k} = \{ u_1, u_2, \dots, u_n \} \subseteq U

10. 결론

WebSocket과 Redis를 결합한 실시간 채팅 시스템은 다음과 같은 장점을 가진다:

  • 낮은 지연 시간으로 실시간성 보장
  • 수평 확장 구조로 대규모 사용자 대응
  • 신뢰성 있는 메시지 처리 및 사용자 상태 관리

특히 Redis Pub/Sub은 다중 서버 간 메시지 브로드캐스트를 간단하게 구현할 수 있도록 하며, 실시간 채팅 시스템의 핵심 역할을 수행한다.

profile
백엔드 개발자

0개의 댓글