声明 :本文部分内容使用AI辅助生成,经人工编辑、审核和补充个人经验。
更新说明 :技术栈版本信息基于 Node.js 18.x / PM2 5.x。
Node.js后端开发踩坑记录:服务监控、定时任务与文件操作 这篇文章是我在2021年进行Node.js后端开发时遇到的一些问题和解决方案的整理。
服务监控与自动重启 服务状态检测脚本 检测Node.js、MongoDB、Nginx服务状态,异常时自动重启并发送邮件通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 const { exec } = require ('child_process' );const nodemailer = require ('nodemailer' );const transporter = nodemailer.createTransport ({ host : 'smtp.qq.com' , port : 465 , secure : true , auth : { user : 'your_email@qq.com' , pass : 'your_auth_code' } }); async function sendAlert (serviceName ) { await transporter.sendMail ({ from : 'your_email@qq.com' , to : 'admin@example.com' , subject : '服务器服务异常告警' , text : `服务 ${serviceName} 异常,时间:${new Date ().toLocaleString()} ` }); } function checkService (command, serviceName ) { return new Promise ((resolve ) => { exec (command, (error, stdout ) => { const count = parseInt (stdout.trim ()) || 0 ; resolve ({ name : serviceName, running : count > 0 , count }); }); }); } async function monitorServices ( ) { const services = [ { cmd : "ps -ef|grep node|grep -v grep|wc -l" , name : 'Node.js' }, { cmd : "netstat -lntup|grep mongod|wc -l" , name : 'MongoDB' }, { cmd : "netstat -lntup|grep nginx|wc -l" , name : 'Nginx' } ]; const results = await Promise .all ( services.map (s => checkService (s.cmd , s.name )) ); for (const result of results) { if (!result.running ) { console .log (`${result.name} 服务异常,准备重启...` ); await sendAlert (result.name ); await restartService (result.name ); } } } async function restartService (serviceName ) { const commands = { 'Node.js' : 'cd /data/nodejs/ && sudo sh api.sh' , 'MongoDB' : 'sudo systemctl restart mongod' , 'Nginx' : 'sudo systemctl restart nginx' }; if (commands[serviceName]) { exec (commands[serviceName], (error ) => { if (error) { console .error (`${serviceName} 重启失败:` , error); } else { console .log (`${serviceName} 重启成功` ); } }); } } setInterval (monitorServices, 60000 );monitorServices ();
Bash版本监控脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/bash code1=`ps -ef|grep node|wc -l` code2=`netstat -lntup|grep mongod|wc -l` code3=`netstat -lntup|grep nginx|wc -l` my_mail (){ mail_list=("111@qq.com" ) now_date=`date "+%Y-%m-%d %T" ` for i in ${mail_list[@]} do echo -e "服务器服务异常,IP 13.127.1.1,$now_date " | mail -s "服务器服务异常" $i done } my_node (){ cd /data/nodejs/ sudo sh xxx-api.sh sudo sh xxx-service.sh } n=1 count=1 while ((n<4 ))do if [ $code1 -gt 3 ] && [ $code2 -gt 0 ] && [ $code3 -gt 0 ]then n=$(($n +1 )) sleep 2 else count=$(($count +1 )) n=$(($n +1 )) sleep 2 if [ $count -eq 3 ];then my_mail if [ $code1 -lt 3 ];then my_node fi fi fi done
PM2进程管理 PM2基础命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 pm2 start app.js pm2 start app.js --name my-api pm2 start app.js -i 4 pm2 status pm2 list pm2 logs pm2 logs app-name pm2 restart app-name pm2 stop app-name pm2 delete app-name pm2 save pm2 startup
PM2定时重启 1 2 3 4 5 pm2 start app.js --node-args="--nouse-idle-notification" --cron '0 0 0 0 0 5' pm2 start app.js --cron '0 0 * * *'
PM2配置文件 ecosystem.config.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 module .exports = { apps : [{ name : 'api-server' , script : './app.js' , instances : 4 , exec_mode : 'cluster' , watch : true , ignore_watch : ['node_modules' , 'logs' ], max_memory_restart : '500M' , env : { NODE_ENV : 'development' , PORT : 3000 }, env_production : { NODE_ENV : 'production' , PORT : 80 }, log_date_format : 'YYYY-MM-DD HH:mm:ss Z' , error_file : './logs/err.log' , out_file : './logs/out.log' , merge_logs : true , cron_restart : '0 3 * * *' , autorestart : true , max_restarts : 10 }] };
解决sudo pm2找不到命令 原因: sudo使用secure_path限制命令路径
解决方案:
1 2 3 cd /usr/binln -s /usr/local/bin/node nodeln -s /usr/local/bin/pm2 pm2
文件系统操作 递归删除目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const fs = require ('fs' );const path = require ('path' );function deleteFolderRecursive (folderPath ) { if (fs.existsSync (folderPath)) { fs.readdirSync (folderPath).forEach ((file ) => { const curPath = path.join (folderPath, file); if (fs.statSync (curPath).isDirectory ()) { deleteFolderRecursive (curPath); } else { fs.unlinkSync (curPath); } }); fs.rmdirSync (folderPath); } } deleteFolderRecursive ('./temp' );
递归复制目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const fs = require ('fs' );const path = require ('path' );function copyFolderRecursive (src, dest ) { if (!fs.existsSync (dest)) { fs.mkdirSync (dest, { recursive : true }); } const entries = fs.readdirSync (src, { withFileTypes : true }); for (const entry of entries) { const srcPath = path.join (src, entry.name ); const destPath = path.join (dest, entry.name ); if (entry.isDirectory ()) { copyFolderRecursive (srcPath, destPath); } else { fs.copyFileSync (srcPath, destPath); } } } copyFolderRecursive ('./source' , './destination' );
递归读取文件内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const fs = require ('fs' );const path = require ('path' );function readFilesRecursive (dir, callback ) { if (!fs.existsSync (dir)) return ; fs.readdirSync (dir).forEach (file => { const filePath = path.join (dir, file); const stat = fs.statSync (filePath); if (stat.isDirectory ()) { readFilesRecursive (filePath, callback); } else { const content = fs.readFileSync (filePath, 'utf-8' ); callback (filePath, content); } }); } const charMap = {};readFilesRecursive ('./src' , (filePath, content ) => { if (filePath.endsWith ('.js' )) { for (const char of content) { charMap[char] = (charMap[char] || 0 ) + 1 ; } } });
创建多级目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function makeDir (dirpath ) { if (!fs.existsSync (dirpath)) { let pathtmp; dirpath.split (path.sep ).forEach (dirname => { if (pathtmp) { pathtmp = path.join (pathtmp, dirname); } else { pathtmp = dirname || path.sep ; } if (!fs.existsSync (pathtmp)) { fs.mkdirSync (pathtmp); } }); } }
执行系统命令 使用child_process模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const { exec, execSync, spawn } = require ('child_process' );exec ('ls -la' , (error, stdout, stderr ) => { if (error) { console .error (`执行出错: ${error} ` ); return ; } console .log (`stdout: ${stdout} ` ); console .error (`stderr: ${stderr} ` ); }); try { const result = execSync ('ls -la' ); console .log (result.toString ()); } catch (error) { console .error (error); } const ls = spawn ('ls' , ['-la' , '/usr' ]);ls.stdout .on ('data' , (data ) => { console .log (`stdout: ${data} ` ); }); ls.stderr .on ('data' , (data ) => { console .error (`stderr: ${data} ` ); }); ls.on ('close' , (code ) => { console .log (`子进程退出,退出码 ${code} ` ); });
使用node-cmd包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const cmd = require ('node-cmd' );const syncData = cmd.runSync ('cd ./example & dir' );console .log (` Sync Err: ${syncData.err} Sync stderr: ${syncData.stderr} Sync Data: ${syncData.data} ` );cmd.run ('ipconfig' , (err, data, stderr ) => { console .log (data); });
性能优化 代码执行时间检测 1 2 3 4 5 console .time ("saveData time" );this .saveData ();console .timeEnd ("saveData time" );
内存使用监控 1 2 3 4 5 6 7 8 9 10 11 12 function printMemoryUsage ( ) { const usage = process.memoryUsage (); console .log ('内存使用情况:' ); console .log (`RSS: ${(usage.rss / 1024 / 1024 ).toFixed(2 )} MB` ); console .log (`堆已用: ${(usage.heapUsed / 1024 / 1024 ).toFixed(2 )} MB` ); console .log (`堆总量: ${(usage.heapTotal / 1024 / 1024 ).toFixed(2 )} MB` ); console .log (`外部: ${(usage.external / 1024 / 1024 ).toFixed(2 )} MB` ); } setInterval (printMemoryUsage, 30000 );
垃圾回收优化 1 2 3 4 5 6 if (global .gc ) { global .gc (); }
流式处理大文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const fs = require ('fs' );const readline = require ('readline' );async function processLargeFile (filePath ) { const fileStream = fs.createReadStream (filePath); const rl = readline.createInterface ({ input : fileStream, crlfDelay : Infinity }); for await (const line of rl) { processLine (line); } }
TypeScript支持 全局安装TypeScript 1 npm install typescript -g
初始化tsconfig.json
tsconfig.json配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "compilerOptions" : { "target" : "ES2020" , "module" : "commonjs" , "outDir" : "./dist" , "rootDir" : "./src" , "strict" : true , "esModuleInterop" : true , "skipLibCheck" : true , "forceConsistentCasingInFileNames" : true , "resolveJsonModule" : true , "declaration" : true , "sourceMap" : true } , "include" : [ "src/**/*" ] , "exclude" : [ "node_modules" , "dist" ] }
VS Code编译配置
编写好.ts文件后
按下Ctrl+Shift+B快捷键
选择监视模式,自动生成对应的.js文件
错误处理 处理EPERM错误 错误: node EPERM operation not permitted
解决方案: 以管理员身份运行CMD或PowerShell
处理Cannot read property错误 解决方案:
全局异常捕获 1 2 3 4 5 6 7 8 9 10 11 12 process.on ('unhandledRejection' , (reason, promise ) => { console .error ('未处理的Promise拒绝:' , reason); }); process.on ('uncaughtException' , (err ) => { console .error ('未捕获的异常:' , err); process.exit (1 ); });
最佳实践 项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 project/ ├── src/ # 源代码 │ ├── config/ # 配置文件 │ ├── controllers/ # 控制器 │ ├── models/ # 数据模型 │ ├── routes/ # 路由 │ ├── services/ # 业务逻辑 │ └── utils/ # 工具函数 ├── tests/ # 测试文件 ├── logs/ # 日志目录 ├── scripts/ # 脚本文件 ├── .env # 环境变量 ├── package.json └── ecosystem.config.js # PM2配置
环境变量管理 1 2 3 4 5 6 7 8 9 10 11 12 13 require ('dotenv' ).config ();module .exports = { port : process.env .PORT || 3000 , mongodb : { uri : process.env .MONGODB_URI || 'mongodb://localhost:27017/mydb' }, redis : { host : process.env .REDIS_HOST || 'localhost' , port : process.env .REDIS_PORT || 6379 } };
以上是我在2021年Node.js后端开发中的一些踩坑记录和解决方案。