Open
Description
Introduction
- When deploying a Node.js application that uses Socket.IO for real-time communication, scaling can be challenging due to the need for session consistency and message broadcasting across multiple instances. Using a Redis adapter and Redis emitter can help manage these challenges effectively.
Prerequisites
- Kubernetes Cluster: A running Kubernetes cluster.
- Redis: A Redis instance accessible by the Node.js application.
- Node.js Application: An existing Node.js application using Socket.IO.
Node Code:
import express from 'express';
import http from 'http';
import { Server } from 'socket.io';
import cors from 'cors';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
import { Emitter } from '@socket.io/redis-emitter';
const app = express();
const server = http.createServer(app);
// Get Redis external IP and port from environment variables
const redisHost = process.env.REDIS_HOST || '192.168.0.49'; // Update this to your Redis IP
const redisPort = Number(process.env.REDIS_PORT) || 31225; // Update this to your Redis port
// Create a Redis client
const redisClient = createClient({ url: `redis://${redisHost}:${redisPort}` });
const subClient = redisClient.duplicate();
// Handle Redis connection errors
redisClient.on('error', (err) => {
console.error('Redis client error:', err);
});
// Set up CORS options for Socket.IO
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
credentials: true,
},
});
// Connect to Redis and configure Socket.IO
redisClient.connect().then(() => {
console.log('Connected to Redis');
// Set the Redis adapter for Socket.IO
io.adapter(createAdapter(redisClient, subClient));
// Create a Redis emitter
const emitter = new Emitter(redisClient);
// Emit a message every 5 seconds
setInterval(() => {
const currentTime = new Date();
console.log('Emitting time:', currentTime);
emitter.emit('time', currentTime.toISOString()); // Emit the current time in ISO format
}, 5000); // Emit every 5 seconds
}).catch((err) => {
console.error('Redis connection error:', err);
});
// Enable CORS for all routes
app.use(cors());
// Handle Socket.IO connections
io.on('connection', (socket) => {
console.log('New client connected');
// Listen for the 'time' event emitted by Redis
socket.on('time', (time) => {
console.log('Time received:', time);
socket.emit('time', time); // Send the time to the connected client
});
// Listen for the 'time' event from the emitter and send to the client
socket.on('subscribeToTime', () => {
console.log('Client subscribed to time updates');
socket.join('time-updates');
});
// Listen for incoming messages and broadcast them
socket.on('sendMessage', (message) => {
io.emit('receiveMessage', message);
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
// Listen to Redis for 'time' events and broadcast them to all clients
subClient.on('message', (channel: string, message: string) => {
if (channel === 'time') {
const time = new Date(message);
io.to('time-updates').emit('time', time); // Emit the time to subscribed clients
}
});
// Subscribe to the 'time' channel
subClient.subscribe('time', (err) => {
if (err) {
console.error('Failed to subscribe to time channel:', err);
} else {
console.log('Subscribed to time channel');
}
});
// Start the server
const PORT = process.env.PORT || 5001;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
App.tsx
Client App code (react js)
import React from 'react';
import { Chat } from './Chat';
import { io } from 'socket.io-client';
// Use an environment variable or fallback to a default URL
console.log(process.env.REACT_APP_SOCKET_URL)
const socket = io(process.env.REACT_APP_SOCKET_URL || 'http://localhost:5001');
console.log(socket)
const App: React.FC = () => {
return (
<div>
<h1>Group Chat Application</h1>
<Chat socket={socket} />
</div>
);
};
export default App;
Chat.tsx
import React, { useEffect, useState } from 'react';
import { Socket } from 'socket.io-client'; // Import the Socket type
interface ChatProps {
socket: Socket; // Use the Socket type here
}
export const Chat: React.FC<ChatProps> = ({ socket }) => {
const [message, setMessage] = useState('');
const [messages, setMessages] = useState<string[]>([]);
useEffect(() => {
socket.on('receiveMessage', (message: string) => {
setMessages((prev) => [...prev, message]);
});
return () => {
socket.off('receiveMessage'); // Clean up the listener on unmount
};
}, [socket]);
const sendMessage = () => {
if (message) {
socket.emit('sendMessage', message);
setMessage('');
}
};
return (
<div>
<div>
{messages.map((msg, index) => (
<div key={index}>{msg}</div>
))}
</div>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
sendMessage();
}
}}
/>
<button onClick={sendMessage}>Send</button>
</div>
);
};