面试题答案
一键面试性能问题原因分析
- 事件监听过多:大量的事件监听器注册会消耗内存和 CPU 资源。每次注册监听器都需要在内部维护数据结构来管理这些监听器,当数量庞大时,内存占用增加,并且遍历这些监听器来触发事件的性能也会下降。
- 事件触发频率高:在高并发场景下,如果事件频繁触发,每次触发都要执行所有注册的监听器,会导致 CPU 使用率飙升。因为频繁的函数调用和上下文切换会带来额外的开销。
- 监听器执行时间长:如果某个或多个监听器的执行逻辑复杂,耗时较长,会阻塞事件循环,导致后续的事件处理延迟,影响整个应用的响应性能。
优化方案
- 减少不必要的监听器:
- 在不需要监听某些事件时,及时移除监听器。例如,当某个特定任务完成后,与之相关的临时监听器就可以取消注册。
- 可以通过使用一次性监听器(
once
方法),在事件第一次触发后自动移除监听器,适用于只需要响应一次的场景。
- 优化监听器逻辑:
- 将复杂的监听器逻辑拆分成多个小的、独立的函数,尽量减少单个监听器的执行时间,避免阻塞事件循环。
- 对于一些耗时较长的操作,可以使用异步操作(如
setTimeout
、Promise
、async/await
),让事件循环可以继续处理其他事件。
- 批量处理事件:
- 对于频繁触发的事件,可以采用批量处理的方式。例如,设置一个定时器,在一定时间间隔内收集事件,然后一次性处理这些事件,而不是每次事件触发都立即处理。
高效高并发任务调度系统示例
const { EventEmitter } = require('events');
class TaskScheduler extends EventEmitter {
constructor(maxConcurrency) {
super();
this.maxConcurrency = maxConcurrency;
this.runningTasks = 0;
this.taskQueue = [];
}
addTask(task) {
this.taskQueue.push(task);
this._processQueue();
}
_processQueue() {
while (this.runningTasks < this.maxConcurrency && this.taskQueue.length > 0) {
const task = this.taskQueue.shift();
this.runningTasks++;
task()
.then(() => {
this.runningTasks--;
this.emit('taskCompleted');
this._processQueue();
})
.catch((error) => {
this.runningTasks--;
this.emit('taskError', error);
this._processQueue();
});
}
}
}
// 使用示例
const scheduler = new TaskScheduler(3);
scheduler.on('taskCompleted', () => {
console.log('A task has been completed.');
});
scheduler.on('taskError', (error) => {
console.log('An error occurred in a task:', error);
});
function task1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 1 completed.');
resolve();
}, 1000);
});
}
function task2() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 2 completed.');
resolve();
}, 2000);
});
}
function task3() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 3 completed.');
resolve();
}, 1500);
});
}
scheduler.addTask(task1);
scheduler.addTask(task2);
scheduler.addTask(task3);
scheduler.addTask(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 4 completed.');
resolve();
}, 2500);
});
});
在上述示例中,TaskScheduler
类继承自 EventEmitter
。通过 addTask
方法将任务添加到任务队列中,_processQueue
方法根据最大并发数来处理任务队列中的任务。当任务完成或出错时,通过 EventEmitter
触发相应的事件,其他模块可以监听这些事件来进行相应的处理。这样可以实现一个高效的高并发任务调度系统。