Skip to content

Commit

Permalink
feat: socket io clustering with adminui
Browse files Browse the repository at this point in the history
  • Loading branch information
abretonc7s committed Mar 1, 2024
1 parent c2c7da7 commit 21e1475
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 90 deletions.
10 changes: 8 additions & 2 deletions packages/sdk-socket-server-next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,21 @@ You can use Docker Compose to run the SDK socket server in either a development
### Running in Development Mode

1. **Start in Development Mode**:
- Use the command: `yarn start:docker:dev`
- This command sets up the environment for development and starts the server along with any other necessary services, like Redis.
- Use the command: `yarn docker:redis:check`
- This command sets up a local redis cluster and connect to it to make sure everything is working.

### Running in Production Mode

1. **Start in Production Mode**:
- Use the command: `yarn start:docker`
- This command sets up the environment for production. It's optimized for performance and stability.

### Run local Redis cluster setup

1. **Start Redis Cluster**:
- Use the command: `yarn start:redis`
- This command sets up the Redis cluster with 3 nodes.

### Ngrok Configuration

Follow the same Ngrok setup as mentioned in the Local Setup section above to expose your Docker Compose-based server.
Expand Down
40 changes: 17 additions & 23 deletions packages/sdk-socket-server-next/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ version: '3.9'

services:

app:
# This service is used to debug the redis cluster
check-redis:
image: node:latest
volumes:
- ./:/usr/src/app
working_dir: /usr/src/app
command: yarn debug:redis
# ports:
# - "4000:4000"

appdev:
image: node:latest
volumes:
- ./:/usr/src/app
working_dir: /usr/src/app
command: yarn debug
ports:
- '4000:4000'

app1:
build:
Expand Down Expand Up @@ -51,37 +59,28 @@ services:
- cache

redis-master1:
# build:
# context: .
# dockerfile: Dockerfile.redis
image: redis:7.2-alpine
command: redis-server --appendonly yes --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --port 6380
command: redis-server --appendonly yes --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --port 6379
environment:
- REDIS_ROLE=master
ports:
- "6380:6380"
- "6380:6379"

redis-master2:
image: redis:7.2-alpine
# build:
# context: .
# dockerfile: Dockerfile.redis
command: redis-server --appendonly yes --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --port 6381
command: redis-server --appendonly yes --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --port 6379
environment:
- REDIS_ROLE=master
ports:
- "6381:6381"
- "6381:6379"

redis-master3:
image: redis:7.2-alpine
# build:
# context: .
# dockerfile: Dockerfile.redis
command: redis-server --appendonly yes --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --port 6382
command: redis-server --appendonly yes --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --port 6379
environment:
- REDIS_ROLE=master
ports:
- "6382:6382"
- "6382:6379"

redis-cluster-init:
image: redis:7.2-alpine
Expand All @@ -92,11 +91,6 @@ services:
- redis-master2
- redis-master3
entrypoint: ["/bin/sh", "/usr/local/bin/init-redis-cluster.sh"]
# environment:
# - "REDISCLI_AUTH=yourpassword" # Optional: Include if your Redis nodes are password-protected
# To connect and debug the cluster use
# docker run -it --network sdk-socket-server-next_default --rm redis redis-cli -c -p 6379 -h redis-master1
# set mykey "Hello, Redis Cluster!"

# cache is used if want to simulate single node redis architecture
cache:
Expand Down
37 changes: 37 additions & 0 deletions packages/sdk-socket-server-next/init-redis-cluster.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
set -e

# Function to check if a redis node is ready
check_redis() {
echo "Checking if Redis node '$1' is ready..."
while true; do
# Attempt to get a PONG response from the Redis node
if redis-cli -h "$1" -p "$2" ping | grep -q "PONG"; then
echo "Redis node '$1:$2' is ready."
break
else
echo "Waiting for Redis node '$1:$2' to be ready..."
sleep 2
fi
done
}


# Wait for all Redis nodes to be ready
echo "Checking readiness of Redis nodes..."

# Since we can't use arrays in sh, we list the nodes directly
check_redis "redis-master1" "6379"
check_redis "redis-master2" "6379"
check_redis "redis-master3" "6379"


# Since we can't pass arrays to redis-cli, we build the cluster create command manually
echo "All Redis nodes are ready. Creating Redis Cluster..."
echo "yes" | redis-cli --cluster create \
redis-master1:6379 \
redis-master2:6379 \
redis-master3:6379 \
--cluster-replicas 0

echo "Redis Cluster created successfully."
6 changes: 5 additions & 1 deletion packages/sdk-socket-server-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
"build:post-tsc": "echo 'N/A'",
"build:pre-tsc": "echo 'N/A'",
"clean": "rimraf dist",
"docker:redis": "docker compose up redis-cluster-init",
"docker:redis:check": "yarn docker:redis && docker compose up check-redis",
"debug": "NODE_ENV=development ts-node --transpile-only src/index.ts",
"debug:redis": "NODE_ENV=development ts-node --transpile-only src/redis-check.ts",
"debug:redis": "cross-env NODE_ENV=development ts-node --transpile-only src/redis-check.ts",
"docker:debug": "yarn docker:redis && docker compose up appdev",
"lint": "yarn lint:eslint && yarn lint:misc --check",
"lint:changelog": "yarn auto-changelog validate",
"lint:eslint": "eslint .",
Expand Down Expand Up @@ -72,6 +75,7 @@
"@types/winston": "^2.4.4",
"@typescript-eslint/eslint-plugin": "^4.20.0",
"@typescript-eslint/parser": "^4.20.0",
"cross-env": "^7.0.3",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
Expand Down
62 changes: 52 additions & 10 deletions packages/sdk-socket-server-next/src/api-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cors from 'cors';
import express from 'express';
import { rateLimit } from 'express-rate-limit';
import helmet from 'helmet';
import Redis from 'ioredis'; // Import ioredis
import { Cluster, ClusterOptions, Redis } from 'ioredis';
import { logger } from './logger';
import { isDevelopment, isDevelopmentServer } from '.';

Expand Down Expand Up @@ -36,17 +36,59 @@ if (redisNodes.length === 0) {
process.exit(1);
}

// export const redisCluster = new Redis.Cluster(redisNodes); // Initialize Redis Cluster
export const pubClient = new Redis.Cluster(redisNodes, {
showFriendlyErrorStack: true,
const redisCluster = process.env.REDIS_CLUSTER === 'true';
let redisClient: Cluster | Redis | undefined;
const redisClusterOptions: ClusterOptions = {
// slotsRefreshTimeout: 2000,
redisOptions: {
tls: {},
password: process.env.REDIS_PASSWORD || 'redis',
// tls: {}, // WARN: enabling tls would fail the client if not setup with correct params
password: process.env.REDIS_PASSWORD,
},
}); // Initialize Redis Cluster
pubClient.on('error', (error) => {
logger.error('Redis error:', error);
});
};

export const getRedisClient = () => {
if (!redisClient) {
if (redisCluster) {
logger.info('Connecting to Redis Cluster');
redisClient = new Cluster(redisNodes, redisClusterOptions);
} else {
logger.info('Connecting to single Redis node');
redisClient = new Redis(redisNodes[0]);
}
}

redisClient.on('error', (error) => {
logger.error('Redis error:', error);
});

redisClient.on('connect', () => {
logger.info('Connected to Redis Cluster successfully');
});

redisClient.on('close', () => {
logger.info('Disconnected from Redis Cluster');
});

redisClient.on('reconnecting', () => {
logger.info('Reconnecting to Redis Cluster');
});

redisClient.on('end', () => {
logger.info('Redis Cluster connection ended');
});

redisClient.on('wait', () => {
logger.info('Redis Cluster waiting for connection');
});

redisClient.on('select', (node) => {
logger.info('Redis Cluster selected node:', node);
});

return redisClient;
};

export const pubClient = getRedisClient();

const app = express();

Expand Down
4 changes: 3 additions & 1 deletion packages/sdk-socket-server-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ configureSocketServer(server)
`socker.io server started development=${isDevelopment} adminUI=${withAdminUI}`,
);

if (isDevelopmentServer && withAdminUI) {
if (withAdminUI) {
logger.info(`Starting socket.io admin ui`);
instrument(ioServer, {
auth: false,
namespaceName: 'admin',
mode: 'development',
});
}
Expand Down
52 changes: 3 additions & 49 deletions packages/sdk-socket-server-next/src/redis-check.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable import/first */
import dotenv from 'dotenv';
import { Cluster } from 'ioredis';

// Dotenv must be loaded before importing local files
dotenv.config();
import { getRedisClient } from './api-config';

import { logger } from './logger';

Expand All @@ -30,61 +29,16 @@ if (redisNodes.length === 0) {
process.exit(1);
}

const redisClusterOptions = {
slotsRefreshTimeout: 2000,
redisOptions: {
tls: {},
password: process.env.REDIS_PASSWORD || 'yourPassword', // Use environment variable for password
},
};

let redisCluster: Cluster | undefined;

function getRedisCluster(): Cluster {
if (!redisCluster) {
redisCluster = new Cluster(redisNodes, redisClusterOptions);
}

redisCluster.on('error', (error) => {
logger.error('Redis error:', error);
});

redisCluster.on('connect', () => {
logger.info('Connected to Redis Cluster successfully');
});

redisCluster.on('close', () => {
logger.info('Disconnected from Redis Cluster');
});

redisCluster.on('reconnecting', () => {
logger.info('Reconnecting to Redis Cluster');
});

redisCluster.on('end', () => {
logger.info('Redis Cluster connection ended');
});

redisCluster.on('wait', () => {
logger.info('Redis Cluster waiting for connection');
});

redisCluster.on('select', (node) => {
logger.info('Redis Cluster selected node:', node);
});

return redisCluster;
}

async function testRedisOperations() {
try {
// Connect to Redis
const cluster = getRedisCluster();
const cluster = getRedisClient();
logger.info('Connected to Redis Cluster successfully');

// Set a key in Redis
const key = 'testKey';
const value = 'Hello, Redis!';
logger.info(`Setting ${key} in Redis`);
await cluster.set(key, value, 'EX', 60); // Set key to expire in 60 seconds
logger.info(`Set ${key} in Redis`);

Expand Down
3 changes: 0 additions & 3 deletions packages/sdk-socket-server-next/src/socket-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ export const configureSocketServer = async (
const hasRateLimit = process.env.RATE_LIMITER === 'true';
const host = hostname();

// Establish connection to Redis server
await pubClient.connect();

logger.info(
`Start socket server with rate limiter: ${hasRateLimit} - isDevelopment: ${isDevelopment}`,
);
Expand Down
15 changes: 14 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8270,6 +8270,7 @@ __metadata:
analytics-node: ^6.2.0
body-parser: ^1.20.2
cors: ^2.8.5
cross-env: ^7.0.3
dotenv: ^16.3.1
eslint: ^7.30.0
eslint-config-prettier: ^8.3.0
Expand Down Expand Up @@ -20286,6 +20287,18 @@ __metadata:
languageName: node
linkType: hard

"cross-env@npm:^7.0.3":
version: 7.0.3
resolution: "cross-env@npm:7.0.3"
dependencies:
cross-spawn: ^7.0.1
bin:
cross-env: src/bin/cross-env.js
cross-env-shell: src/bin/cross-env-shell.js
checksum: 26f2f3ea2ab32617f57effb70d329c2070d2f5630adc800985d8b30b56e8bf7f5f439dd3a0358b79cee6f930afc23cf8e23515f17ccfb30092c6b62c6b630a79
languageName: node
linkType: hard

"cross-fetch@npm:^3.1.4, cross-fetch@npm:^3.1.5":
version: 3.1.8
resolution: "cross-fetch@npm:3.1.8"
Expand Down Expand Up @@ -20317,7 +20330,7 @@ __metadata:
languageName: node
linkType: hard

"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
dependencies:
Expand Down

0 comments on commit 21e1475

Please sign in to comment.