├── .gitignore ├── .nvmrc ├── README.md ├── package.json ├── processes ├── .gitignore ├── lib │ └── processes.ex ├── mix.exs └── mix.lock └── src ├── cluster.js ├── imgs ├── dest │ ├── .gitkeep │ ├── 1541861161748-img.jpg │ ├── 2130-img.jpg │ ├── 2400-img.jpg │ ├── 27060-img.jpg │ └── 8985-img.jpg └── landscape.jpg ├── modules ├── job.js └── log.js └── standard.js /.gitignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | # Created by https://www.gitignore.io/api/node,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=node,visualstudiocode 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | ### VisualStudioCode ### 84 | .vscode/* 85 | !.vscode/settings.json 86 | !.vscode/tasks.json 87 | !.vscode/launch.json 88 | !.vscode/extensions.json 89 | 90 | # End of https://www.gitignore.io/api/node,visualstudiocode -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.7.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Server Clustering 2 | 3 | An easy example about how does Node.js clustering works. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-server-clustering", 3 | "version": "0.0.1", 4 | "description": "A simple Koa clustering example", 5 | "main": "./src/app.js", 6 | "author": "Michele Riva ", 7 | "license": "MIT", 8 | "private": false, 9 | "scripts": { 10 | "start-cluster": "forever ./src/cluster.js", 11 | "start-standard": "forever ./src/standard.js" 12 | }, 13 | "dependencies": { 14 | "jimp": "^0.5.6", 15 | "koa": "^2.6.1", 16 | "koa-router": "^7.4.0" 17 | }, 18 | "devDependencies": { 19 | "forever": "^0.15.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /processes/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | processes-*.tar 24 | 25 | -------------------------------------------------------------------------------- /processes/lib/processes.ex: -------------------------------------------------------------------------------- 1 | defmodule Processes do 2 | alias __MODULE__ 3 | 4 | def sendRequest url do 5 | case HTTPoison.post url, [], [], [timeout: 50_000, recv_timeout: 50_000] do 6 | {:ok, %HTTPoison.Response{status_code: 200, headers: headers}} 7 | -> IO.inspect headers 8 | {:error, %HTTPoison.Error{reason: reason}} 9 | -> IO.inspect {:error, reason} 10 | end 11 | end 12 | 13 | def spawnProcesses number do 14 | for _ <- 1..number do 15 | spawn(fn -> sendRequest('http://localhost:3000/flip') end) 16 | end 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /processes/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Processes.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :processes, 7 | version: "0.1.0", 8 | elixir: "~> 1.7", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | def application do 15 | [ 16 | extra_applications: [:logger] 17 | ] 18 | end 19 | 20 | defp deps do 21 | [ 22 | {:httpoison, "~> 1.4"} 23 | ] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /processes/mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "httpoison": {:hex, :httpoison, "1.4.0", "e0b3c2ad6fa573134e42194d13e925acfa8f89d138bc621ffb7b1989e6d22e73", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, 7 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, 8 | "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, 9 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, 10 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, 11 | } 12 | -------------------------------------------------------------------------------- /src/cluster.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster') 2 | const { cpus } = require('os') 3 | const log = require('./modules/log') 4 | 5 | const isMaster = cluster.isMaster 6 | const numWorkers = cpus().length 7 | 8 | if (isMaster) { 9 | 10 | log(`Forking ${numWorkers} workers`) 11 | const workers = [...Array(numWorkers)].map(_ => cluster.fork()) 12 | 13 | cluster.on('online', (worker) => log(`Worker ${worker.process.pid} is online`)) 14 | cluster.on('exit', (worker, exitCode) => { 15 | log(`Worker ${worker.process.id} exited with code ${exitCode}`) 16 | log(`Starting a new worker`) 17 | cluster.fork() 18 | }) 19 | 20 | } else { 21 | 22 | const Koa = require('koa') 23 | const Router = require('koa-router') 24 | const runJob = require('./modules/job') 25 | const router = new Router() 26 | const app = new Koa() 27 | 28 | router.get('/', async ctx => ctx.body = `PID ${process.pid} listening here!`) 29 | .post('/flip', async ctx => { 30 | const res = await runJob() 31 | ctx.body = res 32 | }) 33 | 34 | app.use(async (ctx, next) => { 35 | await next(); 36 | const rt = ctx.response.get('X-Response-Time'); 37 | log(`${ctx.method} ${ctx.url} - ${rt}`); 38 | }) 39 | .use(async (ctx, next) => { 40 | const start = Date.now(); 41 | await next(); 42 | const ms = Date.now() - start; 43 | ctx.set('X-Response-Time', `${ms}ms`); 44 | }) 45 | .use(router.routes()) 46 | .listen(3000) 47 | 48 | } -------------------------------------------------------------------------------- /src/imgs/dest/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/dest/.gitkeep -------------------------------------------------------------------------------- /src/imgs/dest/1541861161748-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/dest/1541861161748-img.jpg -------------------------------------------------------------------------------- /src/imgs/dest/2130-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/dest/2130-img.jpg -------------------------------------------------------------------------------- /src/imgs/dest/2400-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/dest/2400-img.jpg -------------------------------------------------------------------------------- /src/imgs/dest/27060-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/dest/27060-img.jpg -------------------------------------------------------------------------------- /src/imgs/dest/8985-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/dest/8985-img.jpg -------------------------------------------------------------------------------- /src/imgs/landscape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micheleriva/node-server-clustering/b93f3a283c1d19b207b9d6fd34840eaca832abc8/src/imgs/landscape.jpg -------------------------------------------------------------------------------- /src/modules/job.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const jimp = require('jimp') 3 | const log = require('./log') 4 | 5 | module.exports = function runJob() { 6 | 7 | return new Promise(async (resolve, reject) => { 8 | 9 | const randomNumber = () => Math.floor(Math.random() * 1995) * 15 10 | const destFileName = `${__dirname}/../imgs/dest/${randomNumber()}-img.jpg` 11 | 12 | log(`Copying ${destFileName}`) 13 | fs.copyFileSync(`${__dirname}/../imgs/landscape.jpg`, destFileName) 14 | 15 | log(`Flipping ${destFileName}`) 16 | const image = await jimp.read(destFileName) 17 | image.flip(true, false) 18 | 19 | log(`Deleting ${destFileName}`) 20 | fs.unlink(destFileName, (err) => { 21 | return err ? reject(err) : resolve('success') 22 | }) 23 | 24 | }) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/log.js: -------------------------------------------------------------------------------- 1 | module.exports = function log(args) { 2 | return process.stdout.write(`[${+ new Date()}] - ${args}\n`) 3 | } -------------------------------------------------------------------------------- /src/standard.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const Router = require('koa-router') 3 | const runJob = require('./modules/job') 4 | const log = require('./modules/log') 5 | const router = new Router() 6 | const app = new Koa() 7 | 8 | router.get('/', async ctx => ctx.body = `PID ${process.pid} listening here!`) 9 | .post('/flip', async ctx => { 10 | const res = await runJob() 11 | ctx.body = res 12 | }) 13 | 14 | app.use(async (ctx, next) => { 15 | await next(); 16 | const rt = ctx.response.get('X-Response-Time'); 17 | log(`${ctx.method} ${ctx.url} - ${rt}`); 18 | }) 19 | .use(async (ctx, next) => { 20 | const start = Date.now(); 21 | await next(); 22 | const ms = Date.now() - start; 23 | ctx.set('X-Response-Time', `${ms}ms`); 24 | }) 25 | .use(router.routes()) 26 | .listen(3000) --------------------------------------------------------------------------------