Process daemon
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