مدیریت فرایندها با Cluster در Node.js
ماژول Cluster در Node.js ابزاری است برای اجرای چندین فرایند (process) همزمان از یک برنامهٔ سرور، بهمنظور استفادهٔ بهینه از هستههای CPU و افزایش ظرفیت پاسخگویی. هر Worker یک فرآیند مجزا دارد و حلقهٔ رویداد (event loop) و حافظهٔ خود را دارا است، بنابراین محدودیت تکنخی بودن Node.js با توزیع بار میان چند فرایند کاهش مییابد.
چرا از Cluster استفاده کنیم؟
- استفادهٔ کامل از چند هستهٔ CPU
- افزایش تحمل خطا (اگر یک Worker کرش کند، دیگران به کار ادامه میدهند)
- قابلیت کار با ترافیک بالا و کاهش زمان پاسخ
- قابلیت مدیریت چرخهٔ عمر Workerها (restart، graceful shutdown و غیره)
مفاهیم کلیدی
- Master/Primary: فرایندی که کنترل fork و مدیریت Workerها را برعهده دارد.
- Worker: فرایندهای فرعی که اپلیکیشن را اجرا میکنند و درخواستها را پاسخ میدهند.
- IPC: ارتباط بین فرایندی که با پیامها (worker.send / process.on(‘message’)) انجام میشود.
- Scheduling Policy: شیوهٔ توزیع اتصالات بین Workerها (SCHED_RR یا SCHED_NONE).
نمونهٔ ساده: راهاندازی HTTP Server با Cluster
const cluster = require('cluster');
const http = require('http');
const os = require('os');
if (cluster.isMaster) {
const cpus = os.cpus().length;
console.log(`Master ${process.pid} is running. Forking ${cpus} workers...`);
for (let i = 0; i {
console.log(`Worker ${worker.process.pid} died. Forking a new one.`);
cluster.fork();
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end(`Handled by worker ${process.pid}`);
}).listen(3000);
console.log(`Worker ${process.pid} started`);
}این کد تعداد Workerها را برابر با تعداد هستههای CPU سیستم میکند و برای هر یک یک سرور HTTP اجرا میکند. در صورت کرش کردن یک Worker، Master یک Worker جدید میسازد. هر Worker یک فرآیند مجزا است و درخواستها را پردازش میکند.
نکات فنی و بهینهسازیها
- پیشفرض زمانبندی در بعضی پلتفرمها SCHED_RR (round-robin) است — میتوانید آن را با cluster.schedulingPolicy تنظیم کنید.
- دقت کنید که حافظهٔ اشتراکی وجود ندارد؛ برای اشتراک داده باید از دیتابیس یا حافظهٔ مشترک مانند Redis استفاده کنید.
- نباید Workerها را بیش از حد از تعداد هستهها افزایش دهید؛ این باعث contention و overhead بیشتر میشود.
ارتباط بین Master و Worker
// master.js
const cluster = require('cluster');
if (cluster.isMaster) {
const worker = cluster.fork();
worker.on('message', msg => {
console.log('Message from worker:', msg);
});
worker.send({ cmd: 'status' });
} else {
process.on('message', msg => {
if (msg.cmd === 'status') {
process.send({ pid: process.pid, uptime: process.uptime() });
}
});
}در این مثال Master به یک Worker پیام میفرستد و Worker با استفاده از process.send به Master پاسخ میدهد. این مکانیزم برای اطلاعرسانی، تغییر تنظیمات یا هماهنگی بین فرایندها مناسب است.
Graceful Restart و Shutdown
راهاندازی مجدد بیوقفه (zero-downtime) یا خاموشی کنترلشده برای جلوگیری از قطع سرویس ضروری است. ایدهٔ کلی: ابتدا یک Worker جدید را بسازید، سپس Worker قدیمی را بهتدریج از کار بیندازید و اجازه دهید درخواستهای جاری تکمیل شوند.
const cluster = require('cluster');
const http = require('http');
const os = require('os');
if (cluster.isMaster) {
const cpus = os.cpus().length;
for (let i = 0; i { // مثال: سیگنال برای ریاستارت
const workers = Object.values(cluster.workers);
const restartWorker = (i) => {
if (i >= workers.length) return;
const worker = workers[i];
worker.disconnect();
const timeout = setTimeout(() => worker.kill(), 5000);
worker.on('exit', () => {
clearTimeout(timeout);
const newWorker = cluster.fork();
newWorker.on('listening', () => restartWorker(i + 1));
});
};
restartWorker(0);
});
} else {
const server = http.createServer((req, res) => {
res.end(`Handled by ${process.pid}`);
}).listen(3000);
// اجازه به تکمیل درخواستها هنگام disconnect
process.on('disconnect', () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10000);
});
}در این کد، Master بهنحوی از هر Worker جدا میشود که ابتدا Worker جدید بالا بیاید و پس از آن Worker قدیمی به آرامی متوقف شود. استفاده از server.close باعث میشود که Worker درخواستهای جاری را کامل کند و از پذیرش درخواست جدید خودداری کند.
Sticky Sessions و وضعیت کاربر
اگر اپلیکیشن شما مبتنی بر نشستها (sessions) است و میخواهید اتصال کاربر همواره به یک Worker مشخص برود (مثلاً برای WebSocket یا session ذخیرهشده در حافظهٔ فرایند)، باید از مکانیزم sticky sessions استفاده کنید. دو راه اصلی:
- استفاده از Load Balancer بیرونی (Nginx، HAProxy) که sticky session را مدیریت میکند.
- پیادهسازی sticky routing در سطح Node با استفاده از net.createServer و hash آیپی یا cookie.
معمولاً بهتر است وضعیت نشست در یک فروشگاه متمرکز (Redis) ذخیره شود تا نیازی به sticky session نباشد.
مقایسه کوتاه: Cluster vs worker_threads vs Process Manager
| فناوری | مناسب برای | مزایا | معایب |
|---|---|---|---|
| Cluster | افزایش Throughput و استفاده از CPU | ساده، سازگار با کدهای موجود | فرایندهای جدا؛ حافظه اشتراکی ندارد |
| worker_threads | محاسبات CPU-bound با نیاز به اشتراک حافظه | اشتراک ArrayBuffer و پیامدهی سریع | پیچیدگی همگامسازی، مناسب برای بخشهای محاسباتی |
| Process Manager (PM2) | Deploy و مدیریت پروسهها در تولید | قابلیت cluster، load balancing، مانیتورینگ | ابزار اضافه برای مدیریت |
نکات عملی و توصیههای متخصصی
- در محیط تولید از Process Managerهایی مانند PM2 یا systemd استفاده کنید تا مدیریت crash و رولبک آسانتر باشد.
- برای وضعیتهای مشترک از Redis یا دیتابیس استفاده کنید؛ فرض نگهداشتن session در حافظهٔ Worker خطرناک است.
- قبل از fork کردن، منابعی که باز هستند (Socket، DB connection) را مدیریت کنید؛ بعضی منابع نیاز به recreate در Worker دارند.
- برای تست بار واقعی از ابزارهایی مانند wrk یا k6 استفاده کنید تا تعداد مناسب Worker را تعیین کنید.
جمعبندی
ماژول Cluster راهی استاندارد و قدرتمند برای مدیریت فرایندها در Node.js است که با استفادهٔ درست میتواند بهرهوری CPU و پایداری سرویسها را بهطور قابلتوجهی افزایش دهد. با این حال در کنار مزایا باید به چالشهای همگامسازی و مدیریت وضعیت توجه شود؛ استفاده از ابزارهایی مانند Redis، PM2 و ساختار graceful restart به ایجاد سرویسهای مقیاسپذیر و قابلاطمینان کمک میکند.
آیا این مطلب برای شما مفید بود ؟




