الوضعين · فصل ٣

Cluster Mode — Port واحد لكل الـ Workers

node:cluster · primary · worker · round-robin · instances max
🎯 الهدف: تبني cluster بإيدك، وتفهم إن PM2 في cluster mode هو نفسه الـ primary، فكودك بيشتغل جوه workers بس.

الفكرة

في cluster فيه process رئيسية اسمها primary. الـ primary مش بيخدم requests، شغلته إنه يعمل fork لـ workers ويمسك الـ port. والـ workers كلهم بيشاركوا نفس الـ port، والـ primary بيوزّع الـ connections عليهم بالدور (round-robin، وده الـ default على Linux وmacOS).

port واحد، ومفيش Nginx مطلوب عشان التوزيع.

بإيدك: Node cluster

cluster.js
const cluster = require("node:cluster");const http = require("node:http");const os = require("node:os");if (cluster.isPrimary) {  const count = os.availableParallelism();  console.log(`primary ${process.pid} → ${count} workers`);  for (let i = 0; i < count; i++) cluster.fork();  cluster.on("exit", (worker, code) => {    // رجّع الـ worker لو وقع، مش لو احنا اللي قفلناه    if (!worker.exitedAfterDisconnect) {      console.log(`worker ${worker.process.pid} died (${code}), forking again`);      cluster.fork();    }  });} else {  http    .createServer((req, res) => res.end(`pid ${process.pid}\n`))    .listen(3000);}
  • cluster.fork() من جوه بيستخدم child_process.fork()، بس معاه قناة IPC مع الـ primary. وده اللي بيخلي الـ workers يشاركوا الـ server handle بتاع الـ port.
  • الـ worker لما ينادي listen(3000)، ده مش بيفتح الـ port بنفسه. الطلب بيروح للـ primary، والـ primary هو اللي ماسك الـ port.
  • isPrimary هو الاسم الجديد. أما isMaster فـ deprecated بس لسه شغال.

نفس الكلام بـ PM2: PM2 هو الـ primary

في cluster mode، PM2 نفسه هو الـ primary. كودك بيتشغّل جوه workers بس، فمحتاجش أي سطر من cluster:

ecosystem.config.js
module.exports = {  apps: [    {      name: "api",      script: "./server.js",      exec_mode: "cluster",      instances: "max", // worker لكل core      env: { PORT: 3000 },    },  ],};
cluster: PM2 ماسك الـ port
  1. Client ← PM2: :3000
  2. PM2 ← worker 1
  3. PM2 ← worker 2
  4. PM2 ← worker N

قيم instances

  • "max" أو 0: worker لكل core.
  • -1: كل الـ cores إلا واحد، عشان تسيب core للـ OS أو للداتابيز لو على نفس السيرفر.
  • رقم زي 4: عدد ثابت، وما ينفعش يزيد عن عدد الـ cores (هنشرح ليه في فصل bin/www).

🧪 Lab: اتأكد إن التوزيع شغال

terminal
pm2 start ecosystem.config.jspm2 list                                   # mode = cluster، وكذا سطر لنفس الاسمfor i in $(seq 8); do curl -s --no-keepalive localhost:3000; done

لازم تلاقي الـ pid بيتغيّر بين الردود. ولو كل الردود جت من pid واحد، فغالبًا الـ client بيعيد استخدام نفس الـ connection (keep-alive)، لأن التوزيع بيتم على مستوى الـ connection مش الـ request.

خلّصت الفصل لما

🧠 اختبر نفسك
١) في PM2 cluster mode، كودك بيشتغل primary ولا worker؟
worker. PM2 هو الـ primary.
٢) ليه workers كتير يقدروا يعملوا listen على نفس الـ port؟
لأن الـ primary هو اللي ماسك الـ port فعلًا، والـ workers بياخدوا منه الـ connections عن طريق IPC.
٣) كل الردود جت من نفس الـ pid. ليه؟
غالبًا keep-alive. التوزيع بيحصل لكل connection جديدة مش لكل request.