面试题答案
一键面试可能出现的性能瓶颈和稳定性问题
- 阻塞事件循环:Node.js 是单线程的,信号处理函数如果执行复杂的同步操作,会阻塞事件循环,导致其他 I/O 操作和 WebSocket 消息处理无法及时执行,造成响应延迟。
- 资源释放问题:在高并发场景下,突然的进程终止(如接收到 SIGTERM 信号)可能导致资源(如打开的文件描述符、数据库连接、WebSocket 连接等)没有正确释放,从而引发资源泄漏。
- 竞态条件:如果多个信号同时到达,或者信号处理函数与主业务逻辑同时操作共享资源,可能会引发竞态条件,导致数据不一致。
优化策略
- 避免阻塞事件循环
- 使用异步操作:在信号处理函数中,尽量使用异步函数和回调,避免长时间运行的同步代码。例如,使用
setImmediate
或process.nextTick
将复杂操作推迟到事件循环的下一个周期执行。 - 任务队列:将信号处理任务放入任务队列,然后在主事件循环中异步处理这些任务,确保信号处理函数尽快返回,不阻塞事件循环。
- 使用异步操作:在信号处理函数中,尽量使用异步函数和回调,避免长时间运行的同步代码。例如,使用
- 高效管理资源释放
- 有序释放:在接收到终止信号(如 SIGTERM)时,先停止接受新的连接,然后逐步关闭现有的连接和释放资源。可以使用
Promise
来管理资源释放的顺序,确保所有资源都被正确释放。 - 资源监控:定期检查资源的使用情况,对于长时间未使用的资源主动释放,防止资源泄漏。
- 有序释放:在接收到终止信号(如 SIGTERM)时,先停止接受新的连接,然后逐步关闭现有的连接和释放资源。可以使用
- 处理竞态条件
- 使用锁机制:可以使用
Mutex
或类似的锁机制来保护共享资源,确保在同一时间只有一个信号处理函数或主业务逻辑能够访问共享资源。 - 信号队列:将信号放入队列中,按顺序处理,避免多个信号同时处理共享资源。
- 使用锁机制:可以使用
优化后的代码示例
以下是一个简单的 Node.js WebSocket 服务器示例,展示了如何处理进程信号并优化性能和稳定性:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
// 用于管理连接的数组
const connections = [];
wss.on('connection', (ws) => {
connections.push(ws);
ws.on('close', () => {
const index = connections.indexOf(ws);
if (index > -1) {
connections.splice(index, 1);
}
});
});
// 处理 SIGTERM 信号
process.on('SIGTERM', async () => {
// 停止接受新连接
wss.close();
// 关闭所有现有连接
const closePromises = connections.map((ws) => new Promise((resolve) => {
ws.close(1000, 'Server is shutting down', resolve);
}));
try {
await Promise.all(closePromises);
console.log('All connections closed, server shutting down');
process.exit(0);
} catch (error) {
console.error('Error closing connections:', error);
process.exit(1);
}
});
// 处理 SIGINT 信号(Ctrl+C)
process.on('SIGINT', () => {
process.emit('SIGTERM');
});
在这个示例中:
- 当接收到
SIGTERM
信号时,先关闭 WebSocket 服务器,停止接受新连接。 - 然后,通过
Promise.all
等待所有现有连接关闭后再退出进程,确保资源有序释放。 SIGINT
信号(通常由Ctrl+C
触发)也被捕获并转发为SIGTERM
信号,以统一处理进程终止。