diff --git a/packages/sdk-socket-server-next/README.md b/packages/sdk-socket-server-next/README.md index 01547e2d2..13b6050eb 100644 --- a/packages/sdk-socket-server-next/README.md +++ b/packages/sdk-socket-server-next/README.md @@ -44,8 +44,8 @@ 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 @@ -53,6 +53,12 @@ You can use Docker Compose to run the SDK socket server in either a development - 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. diff --git a/packages/sdk-socket-server-next/docker-compose.yml b/packages/sdk-socket-server-next/docker-compose.yml index 86e4dac94..f546fdf37 100644 --- a/packages/sdk-socket-server-next/docker-compose.yml +++ b/packages/sdk-socket-server-next/docker-compose.yml @@ -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: @@ -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 @@ -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: diff --git a/packages/sdk-socket-server-next/init-redis-cluster.sh b/packages/sdk-socket-server-next/init-redis-cluster.sh new file mode 100644 index 000000000..63f5e994d --- /dev/null +++ b/packages/sdk-socket-server-next/init-redis-cluster.sh @@ -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." diff --git a/packages/sdk-socket-server-next/package.json b/packages/sdk-socket-server-next/package.json index 5f5e8cc87..ca9c689d8 100644 --- a/packages/sdk-socket-server-next/package.json +++ b/packages/sdk-socket-server-next/package.json @@ -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 .", @@ -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", diff --git a/packages/sdk-socket-server-next/src/api-config.ts b/packages/sdk-socket-server-next/src/api-config.ts index f86396aca..77507c9b8 100644 --- a/packages/sdk-socket-server-next/src/api-config.ts +++ b/packages/sdk-socket-server-next/src/api-config.ts @@ -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 '.'; @@ -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(); diff --git a/packages/sdk-socket-server-next/src/index.ts b/packages/sdk-socket-server-next/src/index.ts index df16f0d13..eb2aa7f63 100644 --- a/packages/sdk-socket-server-next/src/index.ts +++ b/packages/sdk-socket-server-next/src/index.ts @@ -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', }); } diff --git a/packages/sdk-socket-server-next/src/redis-check.ts b/packages/sdk-socket-server-next/src/redis-check.ts index 05b18b654..0de944381 100644 --- a/packages/sdk-socket-server-next/src/redis-check.ts +++ b/packages/sdk-socket-server-next/src/redis-check.ts @@ -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'; @@ -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`); diff --git a/packages/sdk-socket-server-next/src/socket-config.ts b/packages/sdk-socket-server-next/src/socket-config.ts index afe9d8a33..0e50071fb 100644 --- a/packages/sdk-socket-server-next/src/socket-config.ts +++ b/packages/sdk-socket-server-next/src/socket-config.ts @@ -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}`, ); diff --git a/yarn.lock b/yarn.lock index 4aaa33d57..dddea30a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 @@ -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" @@ -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: