🚀 Node.js 异步编程模型
Node.js 的核心优势是其非阻塞 I/O 模型。深入理解异步编程是掌握 Node.js 的关键。
💡 核心概念
Node.js 使用事件驱动、非阻塞 I/O 模型,使其轻量且高效。理解 Event Loop 是掌握 Node.js 的关键。
Event Loop 详解
1. Timers 阶段
执行 setTimeout 和 setInterval 的回调
2. Pending Callbacks 阶段
执行一些系统操作的回调(如 TCP 错误)
3. Idle, Prepare 阶段
仅在内部使用
4. Poll 阶段
获取新的 I/O 事件,执行 I/O 相关回调
5. Check 阶段
执行 setImmediate 的回调
6. Close Callbacks 阶段
执行关闭事件的回调(如 socket.on('close', ...))
异步编程演进
回调函数(Callback)
// 回调地狱
fs.readFile('file1.txt', (err, data1) => {
if (err) return handleError(err)
fs.readFile('file2.txt', (err, data2) => {
if (err) return handleError(err)
fs.writeFile('output.txt', data1 + data2, (err) => {
if (err) return handleError(err)
console.log('Done!')
})
})
})Promise
// 使用 Promise 改进
const readFile = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
readFile('file1.txt')
.then(data1 => {
return readFile('file2.txt').then(data2 => [data1, data2])
})
.then(([data1, data2]) => {
return writeFile('output.txt', data1 + data2)
})
.catch(handleError)Async/Await(推荐)
// 使用 Async/Await(最优雅)
async function mergeFiles() {
try {
const data1 = await readFile('file1.txt')
const data2 = await readFile('file2.txt')
await writeFile('output.txt', data1 + data2)
console.log('Done!')
} catch (err) {
handleError(err)
}
}
// 或使用工具函数包装
const fs = require('fs').promises
async function mergeFiles() {
const data1 = await fs.readFile('file1.txt', 'utf8')
const data2 = await fs.readFile('file2.txt', 'utf8')
await fs.writeFile('output.txt', data1 + data2)
console.log('Done!')
}⚔️ Express vs Koa 对比
Express 和 Koa 都是 Node.js 主流框架,但设计理念不同。
| 特性 | Express | Koa |
|---|---|---|
| 异步处理 | Callback / Promise | Async/Await(原生支持) |
| 中间件模型 | 线性 | 洋葱模型(更优雅) |
| 错误处理 | try-catch / 错误中间件 | try-catch(更直观) |
| 内置功能 | 多(路由、静态文件等) | 少(只提供核心功能) |
| 社区生态 | 成熟、丰富 | 较新、增长中 |
| 学习曲线 | 平缓 | 中等 |
Express 示例
Express 中间件与路由
const express = require('express')
const app = express()
// 中间件
app.use(express.json())
app.use(logger())
app.use(authMiddleware())
// 路由
app.get('/api/users', async (req, res, next) => {
try {
const users = await User.findAll()
res.json(users)
} catch (err) {
next(err) // 传递错误到错误中间件
}
})
// 错误中间件(必须有 4 个参数)
app.use((err, req, res, next) => {
console.error(err)
res.status(500).json({ error: 'Internal Server Error' })
})
app.listen(3000)Koa 示例
Koa 中间件(洋葱模型)
const Koa = require('koa')
const Router = require('@koa/router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = new Router()
// 中间件
app.use(bodyParser())
// 自定义中间件(洋葱模型)
app.use(async (ctx, next) => {
const start = Date.now()
await next() // 执行下一个中间件
const duration = Date.now() - start
console.log(`${ctx.method} ${ctx.url} - ${duration}ms`)
})
// 路由
router.get('/api/users', async (ctx) => {
const users = await User.findAll()
ctx.body = users
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)💡 选择建议
新项目推荐使用 Koa(更现代、Async/Await 支持更好);旧项目或需要丰富中间件生态时选择 Express。
🗄️ 数据库操作与 ORM
Node.js 支持多种数据库,包括关系型(MySQL、PostgreSQL)和 NoSQL(MongoDB、Redis)。
使用 Sequelize(ORM)
Sequelize 模型定义与查询
const { Sequelize, DataTypes } = require('sequelize')
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'
})
// 定义模型
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'users',
timestamps: true
})
// 查询
const users = await User.findAll({
where: { status: 'active' },
order: [['createdAt', 'DESC']],
limit: 10
})
// 创建
const user = await User.create({
username: 'john_doe',
email: '[email protected]',
password: hashPassword('password123')
})使用 Mongoose(MongoDB ODM)
Mongoose Schema 与 Model
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/mydb')
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
}, { timestamps: true })
// 实例方法
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password)
}
// 静态方法
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email })
}
const User = mongoose.model('User', userSchema)
// 使用
const user = await User.create({
username: 'john_doe',
email: '[email protected]',
password: await bcrypt.hash('password123', 10)
})⚠️ 性能提示
使用 ORM 方便但可能有性能损耗。对性能要求高的场景,考虑使用原生查询或查询构建器(如 knex)。
🔐 身份认证与授权
安全的身份认证是后端开发的核心。我们通常使用 JWT(JSON Web Token) 实现无状态认证。
JWT 认证实现
注册与登录
const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')
// 注册
router.post('/register', async (ctx) => {
const { username, email, password } = ctx.request.body
// 验证输入
if (!username || !email || !password) {
ctx.status = 400
ctx.body = { error: 'Missing required fields' }
return
}
// 检查用户是否存在
const existingUser = await User.findOne({ where: { email } })
if (existingUser) {
ctx.status = 409
ctx.body = { error: 'Email already exists' }
return
}
// 创建用户(密码自动哈希)
const user = await User.create({
username,
email,
password: await bcrypt.hash(password, 10)
})
// 生成 JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn, '1h' }
)
ctx.status = 201
ctx.body = { token }
})
// 登录
router.post('/login', async (ctx) => {
const { email, password } = ctx.request.body
const user = await User.findOne({ where: { email } })
if (!user) {
ctx.status = 401
ctx.body = { error: 'Invalid credentials' }
return
}
const isValidPassword = await bcrypt.compare(password, user.password)
if (!isValidPassword) {
ctx.status = 401
ctx.body = { error: 'Invalid credentials' }
return
}
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn, '1h' }
)
ctx.body = { token }
})认证中间件
JWT 验证中间件
// 认证中间件
const authMiddleware = async (ctx, next) => {
// 从 Header 获取 Token
const authHeader = ctx.headers.authorization
if (!authHeader) {
ctx.status = 401
ctx.body = { error: 'No token provided' }
return
}
const token = authHeader.split(' ')[1] // "Bearer <token>"
try {
// 验证 Token
const decoded = jwt.verify(token, process.env.JWT_SECRET)
ctx.state.user = decoded // 将用户信息存储在 ctx.state
await next()
} catch (err) {
ctx.status = 401
ctx.body = { error: 'Invalid or expired token' }
}
}
// 使用中间件
router.get('/profile', authMiddleware, async (ctx) => {
const user = await User.findByPk(ctx.state.user.userId)
ctx.body = { id: user.id, username: user.username, email: user.email }
})📡️ RESTFUL API 设计
良好的 API 设计至关重要。遵循 REST 原则,使 API 直观、一致、易于使用。
使用正确的 HTTP 方法
GET - 获取资源;POST - 创建资源;PUT - 更新资源;DELETE - 删除资源
使用名词而非动词
✅ /users ❌ /getUsers
使用复数名词
✅ /users ❌ /user
版本化 API
✅ /api/v1/users
使用正确的 HTTP 状态码
200 - 成功;201 - 创建成功;400 - 错误请求;401 - 未认证;404 - 未找到
完整的 RESTful API 示例
// 用户资源 API
router.get('/api/v1/users', async (ctx) => {
// 获取用户列表(支持分页、过滤)
const { page = 1, limit = 10, status } = ctx.query
const offset = (page - 1) * limit
const where = {}
if (status) where.status = status
const { count, rows } = await User.findAndCountAll({
where,
limit,
offset,
order: [['createdAt', 'DESC']]
})
ctx.body = {
data: rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
totalPages: Math.ceil(count / limit)
}
}
})
router.get('/api/v1/users/:id', async (ctx) => {
// 获取单个用户
const user = await User.findByPk(ctx.params.id)
if (!user) {
ctx.status = 404
ctx.body = { error: 'User not found' }
return
}
ctx.body = user
})
router.post('/api/v1/users', async (ctx) => {
// 创建用户
const { username, email, password } = ctx.request.body
const user = await User.create({ username, email, password })
ctx.status = 201
ctx.body = user
})
router.put('/api/v1/users/:id', authMiddleware, async (ctx) => {
// 更新用户(需要认证 + 授权)
const user = await User.findByPk(ctx.params.id)
if (!user) {
ctx.status = 404
ctx.body = { error: 'User not found' }
return
}
// 检查权限(只能修改自己的信息)
if (user.id !== ctx.state.user.userId) {
ctx.status = 403
ctx.body = { error: 'Forbidden' }
return
}
await user.update(ctx.request.body)
ctx.body = user
})
router.delete('/api/v1/users/:id', authMiddleware, async (ctx) => {
// 删除用户(需要认证 + 授权)
const user = await User.findByPk(ctx.params.id)
if (!user) {
ctx.status = 404
ctx.body = { error: 'User not found' }
return
}
// 检查权限(只能删除自己的账号,或管理员)
if (user.id !== ctx.state.user.userId && !ctx.state.user.isAdmin) {
ctx.status = 403
ctx.body = { error: 'Forbidden' }
return
}
await user.destroy()
ctx.status = 204
})⚡ 性能优化
Node.js 性能优化涉及多个方面:代码优化、缓存策略、数据库优化、负载均衡等。
使用 Redis 缓存
Redis 缓存策略
const redis = require('redis')
const client = redis.createClient()
// 缓存中间件
const cacheMiddleware = (duration) => {
return async (ctx, next) => {
const key = ctx.url
// 尝试从缓存获取
client.get(key, async (err, data) => {
if (data) {
// 缓存命中
ctx.body = JSON.parse(data)
} else {
// 缓存未命中,执行下一个中间件
await next()
// 将结果存入缓存
client.setex(key, duration, JSON.stringify(ctx.body))
}
})
}
}
// 使用缓存
router.get('/api/users', cacheMiddleware(60), async (ctx) => {
const users = await User.findAll()
ctx.body = users
})数据库查询优化
🎯 常见性能问题
- N+1 查询问题:使用
include(Sequelize)或populate(Mongoose)预加载关联数据 - 缺少索引:为经常查询的字段添加索引
- 返回过多数据:使用分页、字段选择(
SELECT)限制返回数据 - 慢查询:使用
EXPLAIN分析查询计划,优化 SQL
解决 N+1 问题
// ❌ 错误示例(N+1 查询)
const users = await User.findAll()
for (const user of users) {
const posts = await Post.findAll({ where: { userId: user.id } })
console.log(user.username, posts.length)
}
// ✅ 正确示例(使用 include 预加载)
const users = await User.findAll({
include: [{
model: Post,
as: 'posts'
}]
})
// ✅ 正确示例(使用 DataLoader 批量加载)
import DataLoader from 'dataloader'
const batchUsers = async (ids) => {
const users = await User.findAll({ where: { id: ids } })
return ids.map(id => users.find(user => user.id === id))
}
const userLoader = new DataLoader(batchUsers)
const posts = await Post.findAll()
const authors = await Promise.all(
posts.map(post => userLoader.load(post.userId))
)使用集群模式
Cluster 模块利用多核 CPU
const cluster = require('cluster')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
// 主进程:fork 工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`)
cluster.fork() // 重启工作进程
})
} else {
// 工作进程:启动服务器
const app = require('./app.js')
app.listen(3000, () => {
console.log(`Worker ${process.pid} started`)
})
}🚀 部署与监控
Node.js 应用部署需要考虑进程管理、日志记录、性能监控等。
使用 PM2 管理进程
PM2 配置与使用
# 安装 PM2
npm install pm2 -g
# 启动应用
pm2 start app.js --name "my-api"
# 查看进程状态
pm2 status
# 查看日志
pm2 logs my-api
# 重启应用
pm2 restart my-api
# 停止应用
pm2 stop my-api
# 删除应用
pm2 delete my-api
# 生成启动脚本(系统重启后自动启动)
pm2 startup
pm2 savePM2 配置文件(ecosystem.config.js)
module.exports = {
apps: [{
name: 'my-api',
script: 'app.js',
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster', // 集群模式
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production',
NODE_OPTIONS: '--max-old-space-size=4096' // 增加内存限制
},
error_file: 'logs/error.log',
out_file: 'logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
autorestart: true,
watch: false
}]
}性能监控
📊 PM2 Monitoring
内置监控:pm2 monit
📈 New Relic
商业 APM 工具,功能强大
🔍 Elasticsearch + Logstash + Kibana (ELK)
日志收集、存储、可视化
📝 总结
Node.js 后端开发涉及的知识面很广,需要持续学习和实践。
核心要点
- 异步编程:掌握 Callback、Promise、Async/Await
- 框架选择:Express(成熟)vs Koa(现代)
- 数据库:使用 ORM(Sequelize、Mongoose)提高开发效率
- 认证:使用 JWT 实现无状态认证
- API 设计:遵循 RESTful 原则
- 性能优化:缓存、数据库优化、集群模式
- 部署:使用 PM2 管理进程,监控应用性能
希望这篇文章能帮助你构建高性能、可扩展的 Node.js 后端服务!🚀