|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. Node.js环境配置
1.1 Node.js版本选择与安装
Node.js有多个版本可供选择,包括LTS(长期支持)版本和Current(最新)版本。在生产环境中,推荐使用LTS版本,因为它们提供更长时间的稳定性和安全更新。
安装Node.js:
在Linux系统上,可以使用NodeSource仓库安装最新LTS版本:
- # 添加NodeSource仓库
- curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
- # 安装Node.js
- sudo apt-get install -y nodejs
复制代码
在macOS上,可以使用Homebrew:
在Windows上,可以从Node.js官网下载安装包。
验证安装:
1.2 使用NVM管理Node.js版本
NVM(Node Version Manager)允许您在同一台机器上安装和管理多个Node.js版本。
安装NVM:
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
复制代码
安装完成后,重新启动终端或运行以下命令使nvm生效:
使用NVM:
- # 安装最新的LTS版本
- nvm install --lts
- # 切换到指定版本
- nvm use 16.13.0
- # 设置默认版本
- nvm alias default 16.13.0
- # 列出已安装的版本
- nvm ls
复制代码
1.3 项目初始化与依赖管理
创建一个新的Node.js项目:
- mkdir my-node-app
- cd my-node-app
- npm init -y
复制代码
package.json配置:
- {
- "name": "my-node-app",
- "version": "1.0.0",
- "description": "My Node.js Application",
- "main": "app.js",
- "scripts": {
- "start": "node app.js",
- "dev": "nodemon app.js",
- "test": "jest"
- },
- "dependencies": {
- "express": "^4.17.1"
- },
- "devDependencies": {
- "nodemon": "^2.0.15",
- "jest": "^27.4.5"
- }
- }
复制代码
安装依赖:
- # 生产依赖
- npm install express
- # 开发依赖
- npm install --save-dev nodemon jest
复制代码
1.4 环境变量配置
使用dotenv包管理环境变量:
创建.env文件:
- PORT=3000
- NODE_ENV=development
- DB_HOST=localhost
- DB_USER=admin
- DB_PASS=password
复制代码
在应用中加载环境变量:
- require('dotenv').config();
- const port = process.env.PORT || 3000;
- const dbHost = process.env.DB_HOST;
- console.log(`Server running on port ${port}`);
- console.log(`Database host: ${dbHost}`);
复制代码
2. 应用开发最佳实践
2.1 项目结构设计
一个良好的项目结构有助于代码维护和扩展:
- my-node-app/
- ├── src/
- │ ├── controllers/ # 控制器
- │ ├── models/ # 数据模型
- │ ├── routes/ # 路由定义
- │ ├── services/ # 业务逻辑
- │ ├── middlewares/ # 中间件
- │ ├── utils/ # 工具函数
- │ └── config/ # 配置文件
- ├── tests/ # 测试文件
- ├── logs/ # 日志文件
- ├── .env # 环境变量
- ├── .gitignore # Git忽略文件
- ├── app.js # 应用入口
- └── package.json # 项目配置
复制代码
2.2 Express.js基础应用
创建一个基本的Express.js应用:
- // app.js
- const express = require('express');
- const bodyParser = require('body-parser');
- const morgan = require('morgan');
- const helmet = require('helmet');
- const app = express();
- // 安全中间件
- app.use(helmet());
- // 日志中间件
- app.use(morgan('combined'));
- // 解析请求体
- app.use(bodyParser.json());
- app.use(bodyParser.urlencoded({ extended: true }));
- // 路由
- app.get('/', (req, res) => {
- res.json({ message: 'Welcome to my Node.js application!' });
- });
- // 错误处理中间件
- app.use((err, req, res, next) => {
- console.error(err.stack);
- res.status(500).json({ message: 'Something went wrong!' });
- });
- const PORT = process.env.PORT || 3000;
- app.listen(PORT, () => {
- console.log(`Server running on port ${PORT}`);
- });
复制代码
2.3 异步处理与错误捕获
Node.js中的异步操作处理非常重要,以下是几种常见的异步处理模式:
回调函数:
- const fs = require('fs');
- fs.readFile('/path/to/file', (err, data) => {
- if (err) {
- console.error('Error reading file:', err);
- return;
- }
- console.log('File data:', data);
- });
复制代码
Promise:
- const readFilePromise = (path) => {
- return new Promise((resolve, reject) => {
- fs.readFile(path, (err, data) => {
- if (err) {
- reject(err);
- return;
- }
- resolve(data);
- });
- });
- };
- readFilePromise('/path/to/file')
- .then(data => console.log('File data:', data))
- .catch(err => console.error('Error reading file:', err));
复制代码
Async/Await:
- const util = require('util');
- const readFile = util.promisify(fs.readFile);
- async function getFileData(path) {
- try {
- const data = await readFile(path);
- console.log('File data:', data);
- return data;
- } catch (err) {
- console.error('Error reading file:', err);
- throw err;
- }
- }
- getFileData('/path/to/file');
复制代码
2.4 日志记录
使用Winston日志库:
配置日志:
- // src/config/logger.js
- const winston = require('winston');
- const { combine, timestamp, printf } = winston.format;
- const logFormat = printf(({ level, message, timestamp }) => {
- return `${timestamp} [${level}]: ${message}`;
- });
- const logger = winston.createLogger({
- level: 'info',
- format: combine(
- timestamp(),
- logFormat
- ),
- transports: [
- new winston.transports.Console(),
- new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
- new winston.transports.File({ filename: 'logs/combined.log' })
- ]
- });
- module.exports = logger;
复制代码
在应用中使用日志:
- const logger = require('./src/config/logger');
- app.get('/', (req, res) => {
- logger.info('Home page accessed');
- res.json({ message: 'Welcome to my Node.js application!' });
- });
- app.use((err, req, res, next) => {
- logger.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
- res.status(500).json({ message: 'Something went wrong!' });
- });
复制代码
3. 部署策略与流程
3.1 版本控制与Git工作流
使用Git进行版本控制:
- # 初始化Git仓库
- git init
- # 创建.gitignore文件
- echo "node_modules\n.env\nlogs\n*.log" > .gitignore
- # 添加文件到暂存区
- git add .
- # 提交更改
- git commit -m "Initial commit"
- # 添加远程仓库
- git remote add origin https://github.com/username/my-node-app.git
- # 推送到远程仓库
- git push -u origin master
复制代码
Git工作流:
1. 创建功能分支:git checkout -b feature/new-feature
2. 进行开发并提交:git add .和git commit -m "Add new feature"
3. 推送分支:git push origin feature/new-feature
4. 创建Pull Request
5. 代码审查后合并到主分支
3.2 CI/CD流水线设置
使用GitHub Actions进行CI/CD:
创建.github/workflows/deploy.yml文件:
- name: Deploy Node.js Application
- on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
- jobs:
- test:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- node-version: [14.x, 16.x]
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
- cache: 'npm'
-
- - run: npm ci
- - run: npm run build --if-present
- - run: npm test
- deploy:
- needs: test
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/main'
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Use Node.js
- uses: actions/setup-node@v2
- with:
- node-version: '16'
- cache: 'npm'
-
- - run: npm ci
- - run: npm run build --if-present
-
- - name: Deploy to server
- uses: appleboy/ssh-action@master
- with:
- host: ${{ secrets.SERVER_HOST }}
- username: ${{ secrets.SERVER_USER }}
- key: ${{ secrets.SERVER_SSH_KEY }}
- script: |
- cd /path/to/app
- git pull origin main
- npm ci --production
- pm2 reload my-node-app
复制代码
3.3 使用PM2管理进程
PM2是Node.js应用的进程管理器,提供负载均衡、日志管理和监控功能。
安装PM2:
创建ecosystem.config.js文件:
- module.exports = {
- apps: [{
- name: 'my-node-app',
- script: 'app.js',
- instances: 'max', // 根据CPU核心数自动设置实例数量
- exec_mode: 'cluster',
- env: {
- NODE_ENV: 'development',
- PORT: 3000
- },
- env_production: {
- NODE_ENV: 'production',
- PORT: 80
- }
- }]
- };
复制代码
启动应用:
- # 开发环境
- pm2 start ecosystem.config.js --env development
- # 生产环境
- pm2 start ecosystem.config.js --env production
复制代码
PM2常用命令:
- # 查看所有应用状态
- pm2 list
- # 停止应用
- pm2 stop my-node-app
- # 重启应用
- pm2 restart my-node-app
- # 删除应用
- pm2 delete my-node-app
- # 查看日志
- pm2 logs my-node-app
- # 监控应用
- pm2 monit
复制代码
3.4 使用Docker容器化应用
创建Dockerfile:
- # 使用官方Node.js运行时作为基础镜像
- FROM node:16-alpine
- # 设置工作目录
- WORKDIR /usr/src/app
- # 复制package.json和package-lock.json
- COPY package*.json ./
- # 安装应用依赖
- RUN npm ci --only=production
- # 复制应用源代码
- COPY . .
- # 暴露端口
- EXPOSE 3000
- # 定义环境变量
- ENV NODE_ENV=production
- # 启动应用
- CMD [ "node", "app.js" ]
复制代码
创建.dockerignore文件:
- node_modules
- npm-debug.log
- .env
- .git
- .gitignore
- README.md
- logs
复制代码
构建Docker镜像:
- docker build -t my-node-app .
复制代码
运行Docker容器:
- docker run -d -p 3000:3000 --name my-node-app-container my-node-app
复制代码
使用Docker Compose:
创建docker-compose.yml文件:
- version: '3'
- services:
- app:
- build: .
- ports:
- - "3000:3000"
- environment:
- - NODE_ENV=production
- - PORT=3000
- depends_on:
- - mongo
- volumes:
- - ./logs:/usr/src/app/logs
- mongo:
- image: mongo:latest
- ports:
- - "27017:27017"
- volumes:
- - mongo-data:/data/db
- volumes:
- mongo-data:
复制代码
启动服务:
4. 生产环境优化
4.1 安全配置
使用Helmet增强安全性:
- const helmet = require('helmet');
- app.use(helmet());
复制代码
配置CORS:
- const cors = require('cors');
- const corsOptions = {
- origin: ['https://example.com', 'https://www.example.com'],
- optionsSuccessStatus: 200,
- credentials: true
- };
- app.use(cors(corsOptions));
复制代码
防止CSRF攻击:
- const csrf = require('csurf');
- const csrfProtection = csrf({ cookie: true });
- app.use(csrfProtection);
- app.get('/form', (req, res) => {
- res.render('send', { csrfToken: req.csrfToken() });
- });
复制代码
限制请求速率:
- const rateLimit = require('express-rate-limit');
- const limiter = rateLimit({
- windowMs: 15 * 60 * 1000, // 15分钟
- max: 100, // 限制每个IP在windowMs内最多100个请求
- message: 'Too many requests from this IP, please try again later.'
- });
- app.use(limiter);
复制代码
4.2 静态资源优化
使用Express静态文件中间件并设置缓存:
- const express = require('express');
- const path = require('path');
- const app = express();
- // 静态文件缓存
- const cacheTime = 86400000; // 1天
- app.use(express.static(path.join(__dirname, 'public'), {
- maxAge: cacheTime,
- etag: true,
- lastModified: true
- }));
复制代码
使用CDN加速静态资源:
- <!-- 使用CDN加载jQuery -->
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
- <!-- 使用CDN加载Bootstrap -->
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
复制代码
4.3 数据库连接优化
使用连接池管理数据库连接:
- const mysql = require('mysql2/promise');
- // 创建连接池
- const pool = mysql.createPool({
- host: process.env.DB_HOST,
- user: process.env.DB_USER,
- password: process.env.DB_PASS,
- database: process.env.DB_NAME,
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0
- });
- // 使用连接池查询数据库
- async function getUsers() {
- const [rows] = await pool.query('SELECT * FROM users');
- return rows;
- }
复制代码
对于MongoDB,使用Mongoose连接池:
- const mongoose = require('mongoose');
- const options = {
- useNewUrlParser: true,
- useUnifiedTopology: true,
- poolSize: 10, // 连接池大小
- bufferMaxEntries: 0, // 连接失败时立即返回错误
- connectTimeoutMS: 10000, // 10秒连接超时
- socketTimeoutMS: 45000 // 45秒socket超时
- };
- mongoose.connect(process.env.MONGODB_URI, options)
- .then(() => console.log('MongoDB connected successfully'))
- .catch(err => console.error('MongoDB connection error:', err));
复制代码
4.4 缓存策略
使用Redis作为缓存层:
配置Redis客户端:
- const redis = require('redis');
- const client = redis.createClient({
- host: process.env.REDIS_HOST,
- port: process.env.REDIS_PORT,
- password: process.env.REDIS_PASSWORD
- });
- client.on('error', (err) => {
- console.error('Redis error:', err);
- });
- // 缓存中间件
- function cache(key, ttl) {
- return async (req, res, next) => {
- try {
- const cachedData = await new Promise((resolve, reject) => {
- client.get(key, (err, data) => {
- if (err) reject(err);
- resolve(data);
- });
- });
- if (cachedData) {
- return res.json(JSON.parse(cachedData));
- }
- res.sendResponse = res.json;
- res.json = (body) => {
- client.setex(key, ttl, JSON.stringify(body));
- res.sendResponse(body);
- };
- next();
- } catch (err) {
- console.error('Cache error:', err);
- next();
- }
- };
- }
- // 使用缓存中间件
- app.get('/api/users', cache('users', 3600), async (req, res) => {
- const users = await getUsersFromDatabase();
- res.json(users);
- });
复制代码
使用内存缓存:
- const NodeCache = require('node-cache');
- const myCache = new NodeCache({ stdTTL: 3600, checkperiod: 600 });
- app.get('/api/data', (req, res) => {
- const cachedData = myCache.get('data');
-
- if (cachedData) {
- return res.json(cachedData);
- }
-
- // 从数据库获取数据
- getDataFromDatabase()
- .then(data => {
- myCache.set('data', data);
- res.json(data);
- })
- .catch(err => {
- console.error('Error fetching data:', err);
- res.status(500).json({ message: 'Error fetching data' });
- });
- });
复制代码
5. 性能调优技术
5.1 代码优化
使用异步编程模式:
- // 避免回调地狱
- async function getUserData(userId) {
- try {
- const user = await User.findById(userId);
- const posts = await Post.find({ userId: user._id });
- const comments = await Comment.find({ postId: { $in: posts.map(p => p._id) } });
-
- return {
- user,
- posts,
- comments
- };
- } catch (err) {
- console.error('Error fetching user data:', err);
- throw err;
- }
- }
复制代码
使用流处理大文件:
- const fs = require('fs');
- const zlib = require('zlib');
- // 压缩文件
- const gzip = zlib.createGzip();
- const source = fs.createReadStream('input.txt');
- const destination = fs.createWriteStream('input.txt.gz');
- source.pipe(gzip).pipe(destination);
- // 处理大文件上传
- app.post('/upload', (req, res) => {
- req.pipe(fs.createWriteStream('uploaded_file.dat'))
- .on('finish', () => {
- res.status(200).json({ message: 'File uploaded successfully' });
- })
- .on('error', (err) => {
- console.error('Error uploading file:', err);
- res.status(500).json({ message: 'Error uploading file' });
- });
- });
复制代码
5.2 内存管理
监控内存使用:
- const memoryUsage = () => {
- const used = process.memoryUsage();
- console.log(`Memory usage:
- RSS: ${Math.round(used.rss / 1024 / 1024 * 100) / 100} MB
- Heap Total: ${Math.round(used.heapTotal / 1024 / 1024 * 100) / 100} MB
- Heap Used: ${Math.round(used.heapUsed / 1024 / 1024 * 100) / 100} MB
- External: ${Math.round(used.external / 1024 / 1024 * 100) / 100} MB`);
- };
- // 定期检查内存使用情况
- setInterval(memoryUsage, 60000); // 每分钟检查一次
复制代码
处理内存泄漏:
- // 使用WeakMap避免内存泄漏
- const cache = new WeakMap();
- function addToCache(obj, data) {
- cache.set(obj, data);
- }
- // 使用事件监听器时记得移除
- const EventEmitter = require('events');
- const emitter = new EventEmitter();
- function setupListener() {
- const handler = () => console.log('Event triggered');
- emitter.on('event', handler);
-
- // 在不需要时移除监听器
- return () => {
- emitter.removeListener('event', handler);
- };
- }
- const cleanup = setupListener();
- // 之后调用cleanup()移除监听器
复制代码
5.3 集群模式
Node.js是单线程的,但可以通过集群模式充分利用多核CPU:
- const cluster = require('cluster');
- const os = require('os');
- if (cluster.isMaster) {
- const cpuCount = os.cpus().length;
- console.log(`Master ${process.pid} is running with ${cpuCount} workers`);
-
- // 创建工作进程
- for (let i = 0; i < cpuCount; i++) {
- cluster.fork();
- }
-
- // 监听工作进程退出事件
- cluster.on('exit', (worker, code, signal) => {
- console.log(`Worker ${worker.process.pid} died with code ${code} and signal ${signal}`);
- console.log('Starting a new worker');
- cluster.fork(); // 重启工作进程
- });
- } else {
- // 工作进程可以共享同一个端口
- const express = require('express');
- const app = express();
-
- app.get('/', (req, res) => {
- res.send(`Hello from Worker ${process.pid}`);
- });
-
- const port = process.env.PORT || 3000;
- app.listen(port, () => {
- console.log(`Worker ${process.pid} started on port ${port}`);
- });
- }
复制代码
5.4 负载均衡
使用Nginx作为反向代理和负载均衡器:
- # /etc/nginx/nginx.conf
- user nginx;
- worker_processes auto;
- error_log /var/log/nginx/error.log;
- pid /run/nginx.pid;
- events {
- worker_connections 1024;
- }
- http {
- log_format main '$remote_addr - $remote_user [$time_local] "$request" '
- '$status $body_bytes_sent "$http_referer" '
- '"$http_user_agent" "$http_x_forwarded_for"';
- access_log /var/log/nginx/access.log main;
- sendfile on;
- tcp_nopush on;
- tcp_nodelay on;
- keepalive_timeout 65;
- types_hash_max_size 2048;
- include /etc/nginx/mime.types;
- default_type application/octet-stream;
- # 上游服务器定义
- upstream nodejs_app {
- server 127.0.0.1:3000;
- server 127.0.0.1:3001;
- server 127.0.0.1:3002;
- # 可以添加更多服务器
- }
- # HTTP服务器
- server {
- listen 80;
- server_name example.com;
- # 重定向到HTTPS(可选)
- return 301 https://$server_name$request_uri;
- }
- # HTTPS服务器
- server {
- listen 443 ssl http2;
- server_name example.com;
- ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
- ssl_protocols TLSv1.2 TLSv1.3;
- ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
- ssl_prefer_server_ciphers off;
- # 静态文件
- location /static/ {
- root /var/www/my-node-app;
- expires 1y;
- add_header Cache-Control "public, immutable";
- }
- # 代理到Node.js应用
- location / {
- proxy_pass http://nodejs_app;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection 'upgrade';
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_cache_bypass $http_upgrade;
- proxy_buffering on;
- proxy_buffer_size 4k;
- proxy_buffers 8 4k;
- proxy_busy_buffers_size 8k;
- proxy_temp_file_write_size 8k;
- }
- }
- }
复制代码
6. 并发处理能力提升
6.1 非阻塞I/O操作
使用异步文件操作:
- const fs = require('fs').promises;
- async function readFiles() {
- try {
- const file1 = await fs.readFile('file1.txt', 'utf8');
- const file2 = await fs.readFile('file2.txt', 'utf8');
- const file3 = await fs.readFile('file3.txt', 'utf8');
-
- return { file1, file2, file3 };
- } catch (err) {
- console.error('Error reading files:', err);
- throw err;
- }
- }
- // 并行读取文件
- async function readFilesParallel() {
- try {
- const [file1, file2, file3] = await Promise.all([
- fs.readFile('file1.txt', 'utf8'),
- fs.readFile('file2.txt', 'utf8'),
- fs.readFile('file3.txt', 'utf8')
- ]);
-
- return { file1, file2, file3 };
- } catch (err) {
- console.error('Error reading files:', err);
- throw err;
- }
- }
复制代码
6.2 使用Worker Threads处理CPU密集型任务
- const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
- function runService(workerData) {
- return new Promise((resolve, reject) => {
- const worker = new Worker(__filename, { workerData });
- worker.on('message', resolve);
- worker.on('error', reject);
- worker.on('exit', (code) => {
- if (code !== 0)
- reject(new Error(`Worker stopped with exit code ${code}`));
- });
- });
- }
- async function main() {
- try {
- const result = await runService({ data: 'some data' });
- console.log(result);
- } catch (err) {
- console.error(err);
- }
- }
- // Worker线程执行的代码
- if (!isMainThread) {
- const { data } = workerData;
-
- // 执行CPU密集型任务
- const result = heavyComputation(data);
-
- // 将结果发送回主线程
- parentPort.postMessage(result);
- }
- function heavyComputation(data) {
- // 模拟CPU密集型任务
- let result = 0;
- for (let i = 0; i < 100000000; i++) {
- result += Math.sqrt(i) * Math.random();
- }
- return { data, result };
- }
- // 主线程
- if (isMainThread) {
- main().catch(console.error);
- }
复制代码
6.3 使用消息队列处理高并发
使用Bull处理队列任务:
创建队列处理器:
- // queueProcessor.js
- const Queue = require('bull');
- const redisConfig = {
- host: process.env.REDIS_HOST || '127.0.0.1',
- port: process.env.REDIS_PORT || 6379
- };
- // 创建队列
- const emailQueue = new Queue('email sending', { redis: redisConfig });
- // 处理队列任务
- emailQueue.process('send', async (job) => {
- const { to, subject, body } = job.data;
-
- try {
- // 模拟发送邮件
- console.log(`Sending email to ${to} with subject "${subject}"`);
-
- // 模拟耗时操作
- await new Promise(resolve => setTimeout(resolve, 2000));
-
- console.log(`Email sent to ${to}`);
- return { status: 'success', recipient: to };
- } catch (err) {
- console.error(`Failed to send email to ${to}:`, err);
- throw err;
- }
- });
- // 监听队列事件
- emailQueue.on('completed', (job, result) => {
- console.log(`Job completed with result:`, result);
- });
- emailQueue.on('failed', (job, err) => {
- console.error(`Job failed with error:`, err);
- });
复制代码
在应用中使用队列:
- // app.js
- const Queue = require('bull');
- const redisConfig = {
- host: process.env.REDIS_HOST || '127.0.0.1',
- port: process.env.REDIS_PORT || 6379
- };
- // 创建队列
- const emailQueue = new Queue('email sending', { redis: redisConfig });
- // 添加发送邮件任务到队列
- app.post('/send-email', async (req, res) => {
- try {
- const { to, subject, body } = req.body;
-
- // 将任务添加到队列
- const job = await emailQueue.add('send', { to, subject, body }, {
- attempts: 3, // 失败重试次数
- backoff: 'exponential', // 指数退避
- removeOnComplete: 10, // 保留10个已完成的任务
- removeOnFail: 5 // 保留5个失败的任务
- });
-
- res.status(202).json({
- message: 'Email queued for sending',
- jobId: job.id
- });
- } catch (err) {
- console.error('Error queuing email:', err);
- res.status(500).json({ message: 'Error queuing email' });
- }
- });
复制代码
6.4 使用WebSocket实现实时通信
使用Socket.IO实现实时通信:
服务器端实现:
- const http = require('http');
- const socketIo = require('socket.io');
- const server = http.createServer(app);
- const io = socketIo(server, {
- cors: {
- origin: "*",
- methods: ["GET", "POST"]
- }
- });
- // 存储在线用户
- const onlineUsers = new Map();
- io.on('connection', (socket) => {
- console.log('New client connected:', socket.id);
-
- // 用户登录
- socket.on('user:login', (userData) => {
- onlineUsers.set(socket.id, userData);
- io.emit('users:update', Array.from(onlineUsers.values()));
- });
-
- // 加入房间
- socket.on('room:join', (roomName) => {
- socket.join(roomName);
- console.log(`User ${socket.id} joined room ${roomName}`);
- });
-
- // 发送消息
- socket.on('message:send', (data) => {
- const { room, message, sender } = data;
-
- // 广播消息到房间内的所有用户
- io.to(room).emit('message:new', {
- message,
- sender,
- timestamp: new Date().toISOString()
- });
- });
-
- // 私人消息
- socket.on('message:private', (data) => {
- const { recipientId, message, sender } = data;
-
- // 找到接收者的socket ID
- let recipientSocketId = null;
- for (const [socketId, user] of onlineUsers.entries()) {
- if (user.id === recipientId) {
- recipientSocketId = socketId;
- break;
- }
- }
-
- if (recipientSocketId) {
- // 发送私人消息
- io.to(recipientSocketId).emit('message:private', {
- message,
- sender,
- timestamp: new Date().toISOString()
- });
- }
- });
-
- // 断开连接
- socket.on('disconnect', () => {
- console.log('Client disconnected:', socket.id);
- onlineUsers.delete(socket.id);
- io.emit('users:update', Array.from(onlineUsers.values()));
- });
- });
- const PORT = process.env.PORT || 3000;
- server.listen(PORT, () => {
- console.log(`Server running on port ${PORT}`);
- });
复制代码
客户端实现:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Socket.IO Chat</title>
- <script src="https://cdn.socket.io/4.4.1/socket.io.min.js"></script>
- <style>
- body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
- .container { max-width: 800px; margin: 0 auto; }
- .chat-box { height: 300px; border: 1px solid #ccc; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
- .message { margin-bottom: 10px; padding: 5px; border-radius: 5px; }
- .message.sent { background-color: #dcf8c6; text-align: right; }
- .message.received { background-color: #f1f1f1; }
- .input-group { display: flex; }
- .input-group input { flex: 1; padding: 10px; border: 1px solid #ccc; }
- .input-group button { padding: 10px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
- .users-list { margin-top: 20px; }
- .user { padding: 5px; margin-bottom: 5px; background-color: #f9f9f9; border-radius: 3px; }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>Socket.IO Chat</h1>
-
- <div>
- <h2>Login</h2>
- <input type="text" id="username" placeholder="Username">
- <button id="login-btn">Login</button>
- </div>
-
- <div>
- <h2>Rooms</h2>
- <input type="text" id="room-name" placeholder="Room name">
- <button id="join-room-btn">Join Room</button>
- </div>
-
- <div class="chat-box" id="chat-box"></div>
-
- <div class="input-group">
- <input type="text" id="message-input" placeholder="Type a message...">
- <button id="send-btn">Send</button>
- </div>
-
- <div class="users-list">
- <h2>Online Users</h2>
- <div id="users-list"></div>
- </div>
- </div>
- <script>
- const socket = io();
- let currentUser = null;
- let currentRoom = null;
-
- // DOM elements
- const usernameInput = document.getElementById('username');
- const loginBtn = document.getElementById('login-btn');
- const roomNameInput = document.getElementById('room-name');
- const joinRoomBtn = document.getElementById('join-room-btn');
- const messageInput = document.getElementById('message-input');
- const sendBtn = document.getElementById('send-btn');
- const chatBox = document.getElementById('chat-box');
- const usersList = document.getElementById('users-list');
-
- // Login
- loginBtn.addEventListener('click', () => {
- const username = usernameInput.value.trim();
- if (username) {
- currentUser = { id: Date.now(), name: username };
- socket.emit('user:login', currentUser);
- usernameInput.disabled = true;
- loginBtn.disabled = true;
- }
- });
-
- // Join room
- joinRoomBtn.addEventListener('click', () => {
- const roomName = roomNameInput.value.trim();
- if (roomName) {
- currentRoom = roomName;
- socket.emit('room:join', roomName);
- addSystemMessage(`Joined room: ${roomName}`);
- }
- });
-
- // Send message
- function sendMessage() {
- const message = messageInput.value.trim();
- if (message && currentRoom) {
- socket.emit('message:send', {
- room: currentRoom,
- message,
- sender: currentUser
- });
- messageInput.value = '';
- }
- }
-
- sendBtn.addEventListener('click', sendMessage);
- messageInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- sendMessage();
- }
- });
-
- // Add message to chat box
- function addMessage(message, isSent = false) {
- const messageDiv = document.createElement('div');
- messageDiv.classList.add('message');
- messageDiv.classList.add(isSent ? 'sent' : 'received');
-
- const sender = isSent ? 'You' : message.sender.name;
- const time = new Date(message.timestamp).toLocaleTimeString();
-
- messageDiv.innerHTML = `
- <div><strong>${sender}</strong> <small>${time}</small></div>
- <div>${message.message}</div>
- `;
-
- chatBox.appendChild(messageDiv);
- chatBox.scrollTop = chatBox.scrollHeight;
- }
-
- // Add system message
- function addSystemMessage(message) {
- const messageDiv = document.createElement('div');
- messageDiv.style.color = '#888';
- messageDiv.style.fontStyle = 'italic';
- messageDiv.textContent = message;
-
- chatBox.appendChild(messageDiv);
- chatBox.scrollTop = chatBox.scrollHeight;
- }
-
- // Update users list
- function updateUsersList(users) {
- usersList.innerHTML = '';
- users.forEach(user => {
- const userDiv = document.createElement('div');
- userDiv.classList.add('user');
- userDiv.textContent = user.name;
- usersList.appendChild(userDiv);
- });
- }
-
- // Socket event listeners
- socket.on('message:new', (message) => {
- const isSent = message.sender.id === currentUser.id;
- addMessage(message, isSent);
- });
-
- socket.on('users:update', (users) => {
- updateUsersList(users);
- });
- </script>
- </body>
- </html>
复制代码
7. 监控与维护
7.1 应用性能监控
使用APM工具如New Relic或Datadog:
配置New Relic:
- // newrelic.js
- exports.config = {
- app_name: ['My Node.js App'],
- license_key: 'YOUR_LICENSE_KEY',
- logging: {
- level: 'info'
- }
- };
复制代码
在应用入口文件中引入:
- require('newrelic');
- const express = require('express');
- const app = express();
- // ...其他代码
复制代码
使用Prometheus和Grafana监控:
实现指标收集:
- const client = require('prom-client');
- // 创建默认指标
- const collectDefaultMetrics = client.collectDefaultMetrics;
- collectDefaultMetrics({ prefix: 'node_app_' });
- // 自定义指标
- const httpRequestDurationMicroseconds = new client.Histogram({
- name: 'node_app_http_request_duration_ms',
- help: 'Duration of HTTP requests in ms',
- labelNames: ['method', 'route', 'code'],
- buckets: [50, 100, 200, 300, 400, 500, 1000]
- });
- // 指标中间件
- app.use((req, res, next) => {
- const end = httpRequestDurationMicroseconds.startTimer();
- res.on('finish', () => {
- end({
- route: req.route.path,
- method: req.method,
- code: res.statusCode
- });
- });
- next();
- });
- // 指标端点
- app.get('/metrics', async (req, res) => {
- try {
- res.set('Content-Type', client.register.contentType);
- res.end(await client.register.metrics());
- } catch (err) {
- res.status(500).end(err);
- }
- });
复制代码
7.2 日志分析
使用ELK Stack(Elasticsearch, Logstash, Kibana)或EFK Stack(Elasticsearch, Fluentd, Kibana)进行日志分析。
配置Winston日志发送到Elasticsearch:
- npm install winston-elasticsearch
复制代码- const { ElasticsearchTransport } = require('winston-elasticsearch');
- const esTransportOpts = {
- level: 'info',
- clientOpts: {
- node: 'http://localhost:9200'
- },
- index: 'node-app-logs'
- };
- const esTransport = new ElasticsearchTransport(esTransportOpts);
- const logger = winston.createLogger({
- transports: [
- new winston.transports.Console(),
- esTransport
- ]
- });
复制代码
7.3 健康检查
实现健康检查端点:
- const mongoose = require('mongoose');
- const redis = require('redis');
- const client = redis.createClient();
- app.get('/health', async (req, res) => {
- const health = {
- uptime: process.uptime(),
- timestamp: Date.now(),
- status: 'OK',
- checks: {
- database: 'OK',
- cache: 'OK'
- }
- };
-
- try {
- // 检查数据库连接
- if (mongoose.connection.readyState !== 1) {
- health.checks.database = 'ERROR';
- health.status = 'ERROR';
- }
-
- // 检查Redis连接
- await new Promise((resolve, reject) => {
- client.ping((err) => {
- if (err) {
- health.checks.cache = 'ERROR';
- health.status = 'ERROR';
- reject(err);
- } else {
- resolve();
- }
- });
- });
-
- const statusCode = health.status === 'OK' ? 200 : 503;
- res.status(statusCode).json(health);
- } catch (err) {
- health.status = 'ERROR';
- res.status(503).json(health);
- }
- });
复制代码
7.4 自动扩展策略
使用Kubernetes进行自动扩展:
创建deployment.yaml:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: node-app
- spec:
- replicas: 3 # 初始副本数
- selector:
- matchLabels:
- app: node-app
- template:
- metadata:
- labels:
- app: node-app
- spec:
- containers:
- - name: node-app
- image: your-registry/node-app:latest
- ports:
- - containerPort: 3000
- resources:
- requests:
- memory: "256Mi"
- cpu: "250m"
- limits:
- memory: "512Mi"
- cpu: "500m"
- env:
- - name: NODE_ENV
- value: "production"
- - name: PORT
- value: "3000"
- livenessProbe:
- httpGet:
- path: /health
- port: 3000
- initialDelaySeconds: 30
- periodSeconds: 10
- readinessProbe:
- httpGet:
- path: /health
- port: 3000
- initialDelaySeconds: 5
- periodSeconds: 5
复制代码
创建Horizontal Pod Autoscaler:
- apiVersion: autoscaling/v2beta2
- kind: HorizontalPodAutoscaler
- metadata:
- name: node-app-hpa
- spec:
- scaleTargetRef:
- apiVersion: apps/v1
- kind: Deployment
- name: node-app
- minReplicas: 3
- maxReplicas: 10
- metrics:
- - type: Resource
- resource:
- name: cpu
- target:
- type: Utilization
- averageUtilization: 70
- - type: Resource
- resource:
- name: memory
- target:
- type: Utilization
- averageUtilization: 80
复制代码
结论
Node.js应用部署和性能优化是一个复杂但至关重要的过程。通过本文介绍的从环境配置到生产上线及性能调优的全攻略,您可以显著提升Node.js应用的响应速度和并发处理能力。
关键要点包括:
1. 合理配置Node.js环境,使用版本管理工具如NVM
2. 遵循最佳实践进行应用开发,包括良好的项目结构和错误处理
3. 实施有效的部署策略,如CI/CD流水线和容器化
4. 在生产环境中进行安全配置和资源优化
5. 应用性能调优技术,包括代码优化、内存管理和集群模式
6. 提升并发处理能力,使用非阻塞I/O、Worker Threads和消息队列
7. 建立完善的监控与维护体系,确保应用稳定运行
通过综合运用这些技术和策略,您可以构建高性能、高可用的Node.js应用,满足现代Web应用的需求。 |
|