Appearance
nodejs
说说你对 Node.js 的理解?优缺点?应用场景?
Node.js 是一个开源且跨平台的 JavaScript 运行时环境,它在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,使得 JavaScript 可以用于服务器端开发。Node.js 采用非阻塞型 I/O 机制,在处理 I/O 操作时不会阻塞进程,而是通过事件通知的方式处理操作结果。这种机制使得 Node.js 在处理高并发场景时表现优秀。
非阻塞异步
Node.js 采用了非阻塞型 I/O 机制,在执行 I/O 操作时不会造成阻塞,当操作完成后通过回调函数以事件的形式通知执行结果。这样可以提高程序的执行效率,使得在执行高并发任务时表现出色。
事件驱动
Node.js 使用事件驱动的编程模型。当有新的请求进来时,请求会被压入一个事件队列中,然后通过事件循环来检测队列中的事件状态变化,一旦有状态变化,就会执行对应的回调函数来处理事件。这使得 Node.js 能够高效处理 I/O 密集型应用。
Node.js 的优缺点
优点:
- 处理高并发场景性能更佳:Node.js 适用于处理大量并发请求的场景,由于其非阻塞 I/O 和事件驱动模型,能够高效地处理并发请求。
- 适合 I/O 密集型应用:Node.js 擅长处理 I/O 密集型应用,其执行效率仍然较高,即使在运行极限时,CPU 占用率仍然较低。
- 轻量快速:Node.js 采用 V8 引擎作为底层,使得其执行速度快,且具有较小的内存占用。
缺点:
- 不适合 CPU 密集型应用:由于 Node.js 是单线程的,如果应用程序中有大量计算密集型任务,会导致阻塞,影响整个应用的性能。
- 单线程:Node.js 采用单线程事件循环,虽然能够高效处理 I/O 密集型应用,但是无法充分利用多核 CPU,不能充分发挥多核处理器的优势。
- 可靠性低:由于 Node.js 是单线程的,一旦某个环节崩溃,整个系统都会崩溃,可靠性较低。
Node.js 的应用场景
- Web 应用程序:对于高并发的网络应用程序,如用户表单收集系统、后台管理系统、实时交互系统、考试系统等,Node.js 表现优越。
- 多人联网游戏:基于 Web、Canvas 等技术的多人联网游戏能够充分利用 Node.js 的高并发特性。
- 实时聊天和直播:基于 Web 的多人实时聊天客户端、聊天室、图文直播等应用。
- 单页面应用程序:对于单页面应用程序,Node.js 能够处理前端与后端的数据交互,提供高效的 API 服务。
- 数据库操作和 API 服务:Node.js 可以用于操作数据库,并为前端和移动端提供基于 JSON 的 API 服务。
说说对中间件概念的理解,如何封装 node 中间件?
中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的。
在 Node.js 中,中间件主要是指封装 HTTP 请求细节处理的方法。例如在 Express、Koa 等 Web 框架中,中间件的本质为一个回调函数,参数包含请求对象、响应对象和执行下一个中间件的函数。在这些中间件函数中,我们可以执行业务逻辑代码,修改请求和响应对象,返回响应数据等操作。
Token 校验中间件
js
// token校验中间件
module.exports = (options) => async (ctx, next) {
try {
// 获取token
const token = ctx.header.authorization;
if (token) {
try {
// 使用verify函数验证token,并获取用户相关信息
await verify(token);
} catch (err) {
console.log(err);
}
}
// 进入下一个中间件
await next();
} catch (err) {
console.log(err);
}
}
日志中间件
js
const fs = require('fs')
// 日志中间件
module.exports = (options) => async (ctx, next) => {
const startTime = Date.now()
const requestTime = new Date()
await next()
const ms = Date.now() - startTime
let logEntry = `${ctx.request.ip} -- ${requestTime} -- ${ctx.method} -- ${ctx.url} -- ${ms}ms`
// 输出日志到文件
fs.appendFileSync('./log.txt', logEntry + '\n')
}
Koa-static 中间件简单实现
js
const fs = require('fs')
const path = require('path')
const mime = require('mime')
const { promisify } = require('util')
// 将 stat 和 access 转换成 Promise
const stat = promisify(fs.stat)
const access = promisify(fs.access)
module.exports = function (dir) {
return async (ctx, next) => {
// 将访问的路由处理成绝对路径,这里要使用 join 因为有可能是 /
let realPath = path.join(dir, ctx.path)
try {
// 获取 stat 对象
let statObj = await stat(realPath)
// 如果是文件,则设置文件类型并直接响应内容,否则当作文件夹寻找 index.html
if (statObj.isFile()) {
ctx.set('Content-Type', `${mime.getType()};charset=utf8`)
ctx.body = fs.createReadStream(realPath)
} else {
let filename = path.join(realPath, 'index.html')
// 如果不存在该文件则执行 catch 中的 next 交给其他中间件处理
await access(filename)
// 存在设置文件类型并响应内容
ctx.set('Content-Type', 'text/html;charset=utf8')
ctx.body = fs.createReadStream(filename)
}
} catch (e) {
await next()
}
}
}
Koa-bodyparser 中间件简单实现
js
// 自定义koa-bodyparser中间件
const querystring = require('querystring')
module.exports = function bodyParser() {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
// 存储数据的数组
let dataArr = []
// 接收数据
ctx.req.on('data', (data) => dataArr.push(data))
// 整合数据并使用 Promise 成功
ctx.req.on('end', () => {
// 获取请求数据的类型 json 或表单
let contentType = ctx.get('Content-Type')
// 获取数据 Buffer 格式
let data = Buffer.concat(dataArr).toString()
if (contentType === 'application/x-www-form-urlencoded') {
// 如果是表单提交,则将查询字符串转换成对象赋值给 ctx.request.body
ctx.request.body = querystring.parse(data)
} else if (contentType === 'applaction/json') {
// 如果是json,则将字符串格式的对象转换成对象赋值给 ctx.request.body
ctx.request.body = JSON.parse(data)
}
// 执行成功的回调
resolve()
})
})
// 继续向下执行
await next()
}
}
在封装中间件时,每个中间件应足够简单,职责单一。中间件的代码编写应高效,必要时通过缓存重复获取数据。
Node.js 中的 Web 框架如 Koa 本身比较简洁,但通过中间件的机制能实现各种需要的功能,使得 Web 应用具备良好的可拓展性和组合性。
通过将公共逻辑处理编写在中间件中,可以避免在每个接口回调中重复编写相同的代码,减少冗余代码,达到类似装饰者模式的效果。
Node 性能如何进行监控以及优化?
Node 性能监控指标
- CPU 占用率:CPU 负载和使用率,反映系统 CPU 繁忙程度。
- 内存占用率:监控 Node 进程的内存使用情况,避免内存泄漏和过度消耗。
- 磁盘 I/O:监控硬盘的读写操作,避免 I/O 瓶颈影响性能。
- 网络:监控网络流量,确保网络连接稳定。
如何监控 Node 性能
- 使用 Node.js 内置工具
- util: 提供了一些实用函数,包括 util.promisify 用于将回调函数转换为 Promise 形式,方便异步编程。
- perf_hooks: 是一个性能度量和监控的工具,提供了 performance 和 performanceObserver 等 API,可以用于测量 Node 应用的性能指标。
- v8: 提供了许多 V8 引擎的信息和统计数据,可以通过 v8.getHeapStatistics()来获取堆内存使用情况。
js
// 使用util.promisify将回调函数转换为Promise形式
const util = require('util')
const fs = require('fs')
const readFileAsync = util.promisify(fs.readFile)
// 使用perf_hooks测量函数执行时间
const { performance } = require('perf_hooks')
async function readLargeFile() {
const start = performance.now()
const data = await readFileAsync(__dirname + '/large-file.txt')
const end = performance.now()
console.log(`读取文件耗时: ${end - start} 毫秒`)
return data
}
readLargeFile()
- 使用开源监控工具
- PM2:是一个成熟的 Node.js 进程管理器,提供了丰富的监控和管理功能,包括 CPU 和内存监控、进程状态查看、错误日志记录等。
- New Relic:是一款功能强大的应用性能监控工具,支持多种语言和平台,提供实时性能数据和报告,帮助定位性能问题。
- AppDynamics:是一种全栈的应用性能管理解决方案,可监控分布式应用、云基础架构和网络性能。
- 自定义监控指标
可以通过日志记录、指标上报等方式实现。
js
// 使用日志记录自定义监控指标
const fs = require('fs')
const http = require('http')
function logMetrics(metric) {
fs.appendFile(
'metrics.log',
`${new Date().toISOString()} - ${metric}\n`,
(err) => {
if (err) {
console.error('写入日志失败', err)
}
}
)
}
// 读取大文件并返回
http
.createServer((req, res) => {
const start = process.hrtime.bigint()
const buffer = fs.readFileSync(__dirname + '/large-file.txt')
const end = process.hrtime.bigint()
const duration = Number(end - start) / 1e6 // 转换为毫秒
logMetrics(`读取文件耗时: ${duration} 毫秒`)
res.end(buffer)
})
.listen(3000)
如何优化 Node 性能
- 使用异步操作
Node.js 的异步非阻塞特性是其高性能的重要原因之一。在编写 Node 应用时,应尽可能使用异步操作,避免阻塞事件循环。
js
// 使用异步操作优化性能
const http = require('http')
const fs = require('fs')
http
.createServer((req, res) => {
fs.readFile(__dirname + '/large-file.txt', (err, data) => {
if (err) {
console.error('读取文件失败', err)
res.statusCode = 500
res.end('读取文件失败')
} else {
res.end(data)
}
})
})
.listen(3000)
- 内存管理优化
Node.js 中的内存管理是关键性能优化的一部分。合理使用内存可以减少垃圾回收的频率,提高性能。
- 使用对象池:通过重复使用对象,避免频繁的对象创建和销毁,减少内存开销。
- 控制内存使用:限制单个请求或任务的内存使用,防止出现内存泄漏和堆栈溢出。
js
// 使用对象池优化内存管理
const fs = require('fs')
const http = require('http')
const bufferPool = []
function createBuffer() {
return fs.readFileSync(__dirname + '/large-file.txt')
}
http
.createServer((req, res) => {
let buffer = bufferPool.pop()
if (!buffer) {
buffer = createBuffer()
}
res.end(buffer)
bufferPool.push(buffer)
})
.listen(3000)
- 使用缓存
缓存是提高 Node 应用性能的有效方式之一。可以使用内存缓存(如 Redis、Memcached)或本地缓存(如 Node 缓存模块)来存储经常使用的数据,减少重复计算和查询。
js
// 使用内存缓存优化性能
const http = require('http')
const fs = require('fs')
const NodeCache = require('node-cache')
const cache = new NodeCache()
http
.createServer(
(
req,
res
) => {
const key = 'large-file-data'
let data = cache.get(key)
if (!data) {
data = fs.readFileSync(__dirname + '/large-file.txt')
cache.set(key, data)
}
res.end(data)
}
)
.listen(3000)
- 使用 CDN 加速
如果 Node.js 应用涉及到静态资源(如 CSS、JavaScript、图片等),可以考虑使用 CDN(内容分发网络)来加速资源的传输,提高页面加载速度。