Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaling a Node.js Application with Socket.IO in Kubernetes #5167

Open
jayrajmeh0 opened this issue Aug 12, 2024 · 0 comments
Open

Scaling a Node.js Application with Socket.IO in Kubernetes #5167

jayrajmeh0 opened this issue Aug 12, 2024 · 0 comments
Labels
question Further information is requested

Comments

@jayrajmeh0
Copy link

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.

Screenshot_796
Screenshot_797

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>
    );
};
@jayrajmeh0 jayrajmeh0 added the to triage Waiting to be triaged by a member of the team label Aug 12, 2024
@darrachequesne darrachequesne added question Further information is requested and removed to triage Waiting to be triaged by a member of the team labels Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants