본문 바로가기
카테고리 없음

채팅 서비스 만들기#1 socket.io, redis로 간단한 채팅 구현

by bieber085 2022. 8. 31.

기존 개발용으로 간단히 구현했던 채팅 서비스를 이번에 redis로 scale-out 가능하게 수정할 일이 생겼다.

이 참에 구현했던 과정을 기록해놔야겠다 생각해서 정리해봤다.

 

  • 웹소켓을 이용해 클라이언트와 서버 사이에서 채팅 메시지 송수신
  • 메시지를 전송할 수 있는 API
  • 채팅 메시지를 db에 저장
  • 사용량에 따라 스케일 아웃 가능

위 기능들이 필요한데 express와 socket.io, redis 채팅 서비스를 만들고 Terraform으로 aws ecs에 배포까지 할 예정이다.

 

websocket 서버의 scale-out

 

 

채팅 서비스는 웹 소켓으로 클라이언트와 통신하고, socket-io로 구현되어있다. Client A, B가 웹소켓 서버를 통해 데이터를 주고 받으면서 서로에게 메시지를 전달할 수 있다.

 

http는 통신의 비연결성 덕분에 스케일 아웃에 큰 문제가 없지만 소켓통신은 그렇지 않기 때문에 문제가 생긴다.

http 서버가 세션을 사용할 때도 마찬가지 문제가 있다. 이때는 세션 클러스터링을 사용하기도 하는데 소켓서버도 redis를 이용해 비슷하게 문제 해결이 가능하다.

 

위 그림처럼 2개 이상의 websocket 서버가 존재하고 어떤 식의 loadbalancer로 부하분산을 하고 있는 상태라면 Client A와 B는 각자 다른 서버에 연결되어 서로 메시지를 주고 받지 못하게 된다.

이 때 redis pub/sub을 이용해서 서로 다른 소켓서버에 연결 된 두 클라이언트의 메시지 교환 역할을 한다.

각 서버가 redis로부터 메시지를 subscription하여 클라이언트에게 전달하고, 클라이언트에게 받은 메시지를 publish하여 서버 간에 메시지를 공유할 수 있다.

 

Redis 인스턴스 생성

redis는 aws elasticache를 사용했다. 개발을 위해 사용할 목적이라 최소사양으로 생성한다.

서브넷 그룹은 vpc를 새로 생성해서 지정했다. 이 서브넷들은 나중에 ecs 노드들이 생성될 곳이기도 하다.

 

 

 

간단한 채팅 서비스 구현

 

chat-test 란 이름의 node package를 생성하고 index.ts 파일을 작성한다.

.env 파일에 호스트와 포트, 토큰을 적어준다.

#chat-test 생성
mkdir chat-test
cd chat-test
npm init

#node package 설치
npm i express socket.io ioredis

#ts package 설치
npm i --save-dev @types/cors @types/express
npm i ts-node
//index.ts

require('dotenv').config();
import { Server } from "socket.io"
import express from 'express';
import http from 'http'
import Redis from 'ioredis'

//redis 채널과 옵션
const CHANNEL = "user-msg"
const redisOptions = {
    host: process.env.REDIS_HOST!,
    port: +process.env.REDIS_PORT!,
    password: process.env.REDIS_PASSWORD!,
    tls: {}
}

//socket.io 클라이언트와 redis 클라이언트 생성
const io = new Server()
const pub = new Redis(redisOptions);
const sub = new Redis(redisOptions);


io.of('/chat').on('connection', (socket) => {
    console.log(`connection from ${socket.handshake.address}`);
    socket.emit('message', "connected!")
    //메시지 수신하면 redis로 메시지 publish
    socket.on("message", async (data) => {
        await pub.publish(CHANNEL, JSON.stringify(data));
    });
});


async function listen() {
    await sub.subscribe(CHANNEL)
    //redis로 메시지 수신하면 소켓으로 메시지 emit
    sub.on("message", (channel, message) => {
        const msg = `got message ${message} from "${channel}" channel`
        io.of('/chat').emit(
            "message",
            msg
        )
    })
    return http.createServer(express()).listen(3001);
}

listen().then(server => {
    io.attach(server);
    console.log(`Server listening`);
})

 

3000, 3001 두 개의 포트로 서버를 시작하고 포스트맨으로 접속해서 테스트를 해봤다.

 

3000번 포트 서비스에서 메시지 전송

 

3001번 포트 서비스에서 메시지 수신

서로 다른 서비스 사이에서 메시지를 주고받는 걸 확인할 수 있다.