- 서버-클라이언트 간의 실시간 양방향 통신을 가능하게 하는 라이브러리입니다. 주로 웹 애플리케이션에서 클라이언트와 서버 간의 실시간 데이터 전송을 위해 사용이 됩니다. - 이는 WebSocket 프로토콜을 사용하지만, 브라우저 호환성을 위해 필요한 경우에는 풀백(fallback) 메커니즘을 사용합니다.
https://socket.io/
[ 더 알아보기 ] 💡 풀백 메커니즘
- 주어진 기술이나 프로토콜이 작동하지 않을 경우, 대체할 수 있는 다른 방법을 사용하는 것을 의미합니다. - 예를 들어, Socket.io는 WebSocket을 기본적으로 사용하지만, 만약 클라이언트의 브라우저가 WebSocket을 지원하지 않는다면, HTTP 롱 폴링 등 다른 통신 방식을 사용하여 여전히 실시간 통신을 가능하게 합니다.
💡[참고] 상세한 Socket.io에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
💡PM2(Process Manager 2) - Node.js 기반의 애플리케이션의 프로세스 관리를 위한 ‘프로세스 관리자’를 의미합니다. - 이는 내장된 로드 밸런서(Load Balancer)를 기반으로 애플리케이션이 계속 유지되고, 다운타임 (Downtime) 없이 다시 로드하여 시스템 관리를 용이하게 할 수 있습니다.
- NoSQL 데이터베이스 중 하나이며 오픈소스 소프트웨어입니다. 주요한 특징은 ‘키-값(Key-Value)’ 형태로 데이터를 저장하고 이 데이터를 ‘인-메모리 데이터 저장소’에 저장합니다.
- ‘키-값(Key-Value)’ 형태로 데이터를 저장을 하며 ‘다양한 종류의 값’을 지정 가능합니다. 값으로는 문자열, 리스트, 셋, 정렬된 셋, 해시 등을 지원합니다. - ‘인 메모리 데이터 저장소’를 사용하기에 서버의 메인 메모리에 모든 데이터를 저장하므로, 디스크 I/O를 거치는 다른 데이터베이스 시스템보다 훨씬 빠른 성능을 보여줍니다. - 메모리에 저장되는 데이터 베이스는 디스크에 저장하여 휘발성으로 사용되는 데이터를 영구적으로 저장하는 기능을 제공하여 서버가 다운되더라도 데이터를 복구할 수 있습니다. - 주로 데이터베이스, 캐시, 메시지 브로커 등 다양한 용도로 사용될 수 있습니다.
1. 앨리스에게 채팅 메시지를 보냅니다(send a chat message to Alice) - 밥이 앨리스에게 메시지를 작성하고 전송합니다.
2. "messages" 주제로 메시지를 추가하고 발행합니다(add a message, publish to “messages” topic) - 서버는 받은 메시지를 "messages" 주제로 Redis에 발행합니다. 이를 통해 다른 서버 인스턴스들도 해당 메시지를 받을 수 있습니다.
3. 앨리스가 메시지를 받습니다(Alice receives the message) - 앨리스의 클라이언트는 서버로부터 새 메시지를 수신하고 화면에 표시합니다.
4. Socket.io Redis Adapter 구조 활용 예시
💡Socket.io Redis Adapter 구조 활용 예시 1. 발행자 클라이언트 생성 - createClient()을 통해서 Redis 서버에 연결할 발행자(Publisher) 클라이언트를 생성합니다. 해당 과정에서는 6379 포트의 Redis 서버에 연결합니다.
2. 구독자 클라이언트 생성 - pubClient를 복제하여 subClient를 생성합니다. 이는 Redis의 발행/구독 기능을 위해 사용됩니다.
3. 클라이언트 연결 - 두 클라이언트(pubClient와 subClient)를 Redis 서버에 비동기적으로 연결합니다.
4. Redis 어댑터 설정 - createAdapter 함수를 사용하여 Redis 어댑터를 생성하고, 이를 Socket.IO 서버의 어댑터로 설정합니다.
5. 서버 리스닝 - Socket.IO 서버가 3000번 포트에서 리스닝을 시작합니다.
import { createClient } from "redis";
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
const pubClient = createClient({ url: "redis://localhost:6379" }); // 1. 발행자(Publisher) 클라이언트 생성
const subClient = pubClient.duplicate();// 2. 구독자(Subscriber) 클라이언트 생성
// 3. 클라이언트(pubClient, subClient)를 Redis 서버에 연결합니다.
await Promise.all([
pubClient.connect(),
subClient.connect()
]);
// 4. Redis 어댑터 설정
const io = new Server({
adapter: createAdapter(pubClient, subClient)
});
// 5. 서버 리스닝
io.listen(3000);
- Socket.io 소켓 서버 환경에서 PM2를 이용하여 멀티 프로세스를 지원하는 형태로 구성을 하였을 때, Redis와의 연결도 포함이 되었습니다. - 실제적으로 아래의 Redis의 역할을 통해서 분산 환경에서 Socket.io 애플리케이션의 확장성, 안정성, 그리고 실시간 성능을 크게 향상합니다.
💡homebrew로 redis를 설치합니다 - 간단하게 homebrew를 이용하여 설치를 합니다. - 설치를 하여 실행을 할 때는 foreground 상태에서 내가 서버를 킨 상태인 경우에 수행이 되는 경우와 background 상태에서 PC를 키는 경우 수행되는 경우 두 개의 서비스 실행방법에 대해 알아봅니다.
# redis 설치
$ brew install redis
1.2. 설치를 확인합니다.
# redis의 버전을 확인합니다.
$ redis-cli --version
1.3. 설치된 Redis를 실행합니다.(background)
# background 상태에서 redis를 실행합니다.
$ brew services start redis
# background 상태에서 실행되는
$ brew services info redis
- cors, express, socket.io : 소켓 통신에 이용하는 기본적인 설치 합니다 - @socket.io/redis-adapter : 소켓 통신을 유지하기 위한 redis와의 사용을 위해 설치합니다. - pm2 : 클러스터 모드, 로드밸런싱을 위해 설치합니다. - nodemon: 로컬 환경에서 수정사항을 즉시 반영하기 위해 설치합니다.
# 소켓 연결 및 소켓 연결을 유지하기 위한 redis를 설치합니다.
$ npm i cors express socket.io @socket.io/redis-adapter
# 전역 설치 : 프로세스 매니저를 수행하도록 설치합니다.
$ npm i -g pm2
# nodemon을 개발 단계에서 사용하도록 설치합니다.
$ npm i nodemon --save-dev
3. 기존의 구성한 ecosystem.config.js 내에 로컬 연결 환경을 지정합니다.
💡기존의 구성한 ecosystem.config.js 내에 로컬 연결 환경을 지정합니다.
- apps.env.REDIS_HOST와 apps.env.REDIS_HOST_PORT를 각각 지정하였고, 테스트에서는 앱 이름 “app-dev-redis”를 이용합니다. - 이 설정을 통해 Redis를 사용하는 Socket.io 애플리케이션을 개발 환경에서 클러스터 모드로 실행하기 위한 구성을 마칩니다.
💡Socket.io + Redis 구성을 설정합니다. 1. WebSocket Server 설정 : port, cors 지정 - 해당 서버로 접속할 포트를 지정하고, cors()를 통해서 다른 도메인에서의 모든 요청에 대해서 허용합니다.
2. WebSocket Server 설정값 및 socket.io 구성 - HTTP 서버를 생성하고 지정된 포트로 접근 가능하도록 설정합니다. - transports 속성을 통해서 웹 소켓 연결에 대해서 허용을 하였습니다. - 생성된 HTTP 서버를 기반으로 소켓 서버를 설정하고 socket.io에 대한 cors에 대해 모두 허용을 설정합니다.
3. Redis 설정합니다.(Pub/Sub Redis 연결 구성) - 발행자 클라이언트를 Redis와의 접속으로 연결하여 생성하며, 구독자 클라이언트는 이를 복사하여 생성합니다. - 이 발행자와 구독자를 연결하여 io 서버에 Redis Adapter를 설정합니다.
4. 루트 엔드포인트로 접속 시 health Check 수행 - 루트 경로(”/”)로 접근 시 “Hello world!” 메시지를 출력하고 로그를 출력합니다.
5. 소켓 연결 시작 -"message" 이벤트 발생 시, "message" 이벤트를 등록한 소켓에게 메시지 전달합니다. -"disconnect" 이벤트 발생 시, 소켓 연결을 종료 및 방을 나갑니다.
// import library
const app = require('express')(); // 외부 라이브러리 사용
const cors = require('cors'); // 외부 라이브러리 사용
const http = require('http'); // Node 제공 모듈 사용
const redis = require('redis');
const redisAdapter = require('@socket.io/redis-adapter');
/*
* 1. WebSocket Server 설정 : port, cors 지정
*/
const port = process.env.PORT;
app.set('port', port); // 웹 소켓 포트를 지정합니다
app.use(cors({ origin: '*' })); // cors를 지정해줍니다.
/*
* 2. WebSocket Server 설정값 및 socket.io 구성
*/
const server = http.createServer(app).listen(port); // 설정 값에 따르는 서버 정보 구축
// 설정 값에 따라 socket.io 구축
const io = require('socket.io')(server, {
transports: ['websocket'], // 웹소켓 허용
cors: {
origin: '*',
methods: ['*'], // 모든 메서드 허용
},
});
/*
* 3. Redis 설정합니다.(Pub/Sub Redis 연결 구성)
*/
// 3.1. 발행자(Publisher) 클라이언트 생성
const pubClient = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_HOST_PORT,
});
// 3.2. 구독자(Subscriber) 클라이언트 생성
const subClient = pubClient.duplicate();
// 3.3. Redis 클라이언트 연결
Promise.all([pubClient.connect(), subClient.connect()])
.then(() => io.adapter(redisAdapter.createAdapter(pubClient, subClient))) // Redis adapter 설정
.catch((error) => console.error('Redis connection error:', error));
/*
* 4. 루트 엔드포인트로 접속 시 health Check 수행
*/
app.get('/', (req, res, next) => {
res.send('Hello world!!!!!!'); // 화면상으로 출력
console.log('====== Health Check ======='); // 로그 출력
});
/*
* 5. 소켓 연결 시작
*/
io.on('connection', (socket) => {
console.log('a user connected');
/**
* [ALL] "message" 이벤트 발생 시, "message" 이벤트를 등록한 소켓에게 메시지 전달합니다.
*/
socket.on('message', (msg) => {
io.emit('message', msg);
});
/**
* [ALL] "disconnect" 이벤트 발생 시, 소켓 연결을 종료 및 방을 나갑니다.
*/
socket.on('disconnect', () => {
console.log('a user disconnected');
socket.disconnect();
socket.leave(socket.room);
});
});
- socket/redis/app.js 경로에 파일을 구성하였고, pacakge.json 파일 내에는 “start:redis”라는 명령어로 스크립트를 구성하였습니다.
💡스크립트 설명
- “start:redis”라는 스크립트 명령어를 정의하였습니다. - 이는 PM2를 사용하여 애플리케이션을 실행하며 사전에 구성한 ecosystem 설정 파일을 “./config/ecosystem.config.js"참조하며, -only app-dev 옵션을 통해서 ‘app-dev’ 애플리케이션 설정말 실행하도록 지정하였습니다. - 이를 통해서 Redis와 Socket.io를 사용하는 Node.js 애플리케이션이 PM2를 통해 클러스터 모드로 실행됩니다.
- 왼쪽 클라이언트와 오른쪽 클라이언트 간의 ‘채팅’ 소켓 통신을 주고받고 있습니다. - 왼쪽 클라이언트는 “안녕하세요”라고 소켓 통신을 전달하면 오른쪽 클라이언트에 전달이 되고, 오른쪽 클라이언트는 “안녕하세요 저도 반갑습니다”라고 소켓통신을 전달하면 왼쪽 클라이언트에게 전달이 되어서 서로 간의 데이터 통신이 이루어짐을 확인하였습니다.
3. Redis 모니터링 데이터 해석
💡Redis 모니터링 데이터 해석
- Redis 모니터링 시 나타나는 데이터는 Socket.IO가 Redis를 통해 메시지를 발행(publish)하는 과정을 보여줍니다.