阿里云主机折上折
  • 微信号
Current Site:Index > Process daemon

Process daemon

Author:Chuan Chen 阅读数:11400人阅读 分类: Node.js

The Concept of Process Daemonization

Process daemonization is a mechanism to ensure the continuous operation of critical processes. When a target process unexpectedly exits, the daemon process can automatically restart it. In a Node.js environment, this technique is particularly important because the single-threaded nature means uncaught exceptions can cause the entire application to crash.

Why Process Daemonization is Needed

Node.js applications can crash for various reasons: memory leaks, unhandled exceptions, external dependency failures, etc. In a production environment, manual intervention is impractical. For example:

// Simulating a crash-prone server
const http = require('http');
const server = http.createServer((req, res) => {
  if (Math.random() > 0.8) {
    throw new Error('Simulated crash');
  }
  res.end('OK');
});
server.listen(3000);

This server has a 20% chance of crashing. Without a daemon mechanism, the service would become completely unavailable.

Native Implementation

Using the child_process module, a basic daemon can be built:

const { spawn } = require('child_process');

function startWorker() {
  const worker = spawn('node', ['server.js']);
  
  worker.on('exit', (code) => {
    console.log(`Worker exited with code ${code}`);
    setTimeout(startWorker, 1000); // Restart after 1 second
  });
}

startWorker();

While simple, this approach lacks advanced features like log rotation or cluster management.

Using PM2 for Process Management

PM2 is the most popular process management tool in the Node.js ecosystem. Basic usage after installation:

npm install pm2 -g
pm2 start server.js -i max --name "my-api"

Key features include:

  • -i max: Launches the maximum number of instances based on CPU cores
  • Log management: pm2 logs
  • Monitoring dashboard: pm2 monit
  • Auto-start on boot: pm2 startup

Example configuration file (ecosystem.config.js):

module.exports = {
  apps: [{
    name: 'api',
    script: './server.js',
    instances: 4,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'production'
    }
  }]
}

Considerations for Cluster Mode

Special handling is required when using Node.js clustering:

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  // The master process only handles forking
  for (let i = 0; i < os.cpus().length; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.id} died`);
    cluster.fork();
  });
} else {
  // Worker processes execute business logic
  require('./server');
}

In this mode, each worker process is independent, and a crash won't affect others.

Daemon Strategies in Docker Environments

For containerized deployments, combine with Docker's restart policy:

FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["pm2-runtime", "ecosystem.config.js"]

Then run the container with:

docker run -d --restart unless-stopped my-node-app

The unless-stopped policy ensures the container restarts automatically if it exits abnormally.

Best Practices for Exception Handling

Robust error handling reduces crash likelihood:

process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err);
  // Log detailed error
  // Attempt graceful shutdown
  process.exit(1);
});

process.on('unhandledRejection', (reason) => {
  console.error('Unhandled Promise rejection:', reason);
});

Combined with Domain-Driven Design, crash boundaries can be defined:

app.use('/api', (req, res, next) => {
  try {
    // Business logic
  } catch (err) {
    domain.create().run(() => {
      // Isolate the error
      errorHandler(err, req, res);
    });
  }
});

Health Check Mechanisms

A comprehensive daemon system requires health checks:

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'UP',
    memory: process.memoryUsage(),
    uptime: process.uptime()
  });
});

// Add to PM2 configuration
module.exports = {
  apps: [{
    // ...
    watch: true,
    autorestart: true,
    max_restarts: 10,
    min_uptime: '5s',
    listen_timeout: 5000
  }]
}

Advanced Monitoring Integration

Integrate with APM tools for deep monitoring:

const apm = require('elastic-apm-node').start({
  serviceName: 'my-service',
  serverUrl: 'http://apm-server:8200'
});

app.use((req, res, next) => {
  const transaction = apm.startTransaction(req.path);
  res.on('finish', () => {
    transaction.end();
  });
  next();
});

Log Collection Strategies

An effective logging system helps quickly diagnose issues:

const { createLogger, transports } = require('winston');
const logger = createLogger({
  transports: [
    new transports.File({ 
      filename: 'combined.log',
      format: format.combine(
        format.timestamp(),
        format.json()
      )
    })
  ]
});

// Integrate with PM2 configuration
module.exports = {
  apps: [{
    // ...
    error_file: '/var/log/node-app/err.log',
    out_file: '/var/log/node-app/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm Z'
  }]
}

Resource Limit Management

Prevent system crashes due to memory leaks:

const v8 = require('v8');

setInterval(() => {
  const heapStats = v8.getHeapStatistics();
  if (heapStats.used_heap_size > 500 * 1024 * 1024) {
    console.error('Memory threshold exceeded, initiating restart');
    process.exit(1); // Let the daemon restart the process
  }
}, 30000);

PM2 can directly set memory limits:

pm2 start app.js --max-memory-restart 500M

Zero-Downtime Deployment Strategies

Achieve seamless updates:

// In PM2 configuration
module.exports = {
  apps: [{
    // ...
    exec_mode: 'cluster',
    wait_ready: true,
    kill_timeout: 5000,
    shutdown_with_message: true
  }]
}

// Application readiness signal
process.send('ready');

Deploy using:

pm2 reload ecosystem.config.js

Multi-Environment Configuration Management

Different environments require different daemon strategies:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'app',
    script: 'server.js',
    env: {
      NODE_ENV: 'development',
      WATCH: true
    },
    env_production: {
      NODE_ENV: 'production',
      INSTANCES: 'max'
    }
  }]
}

Specify the environment when starting:

pm2 start ecosystem.config.js --env production

Custom Daemon Processes

For special requirements, a custom solution may be needed:

const fs = require('fs');
const { spawn } = require('child_process');

class Daemon {
  constructor(script) {
    this.script = script;
    this.restartCount = 0;
    this.maxRestarts = 10;
  }

  start() {
    this.worker = spawn('node', [this.script]);
    
    this.worker.stdout.on('data', (data) => {
      fs.appendFileSync('app.log', data);
    });

    this.worker.on('exit', (code) => {
      if (this.restartCount++ < this.maxRestarts) {
        setTimeout(() => this.start(), 1000);
      }
    });
  }
}

new Daemon('server.js').start();

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:多核CPU利用

下一篇:性能与扩展性

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.