socketio / socketio/socket.io

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

Open
#5,167 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

question
Dominant language
TypeScript
Stars
63.2k
Forks
10.3k
Avg merge
11d 20h
Merged PRs (30d)
2

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.

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>
    );
};

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue provides application snippets for a Node.js, React, Redis, and Kubernetes setup but names no repository file, test, or concrete requested change. Start by clarifying whether this is intended as documentation and where it should live; done criteria are not specified.

Written by the indexing model from the issue text.

Assessment

Tech stack
kubernetes, nodejs, react, redis, typescript
Domain
backend, distributed-systems, frontend, infrastructure
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
15/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.