MST
星途 面试题库

面试题:Node.js EventEmitter 在高并发场景下的优化与应用

在高并发的 Node.js 应用场景中,EventEmitter 可能会面临性能瓶颈。请分析可能出现性能问题的原因,并提出至少两种优化方案。同时,举例说明如何运用 EventEmitter 设计一个高效的高并发任务调度系统。
10.6万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

性能问题原因分析

  1. 事件监听过多:大量的事件监听器注册会消耗内存和 CPU 资源。每次注册监听器都需要在内部维护数据结构来管理这些监听器,当数量庞大时,内存占用增加,并且遍历这些监听器来触发事件的性能也会下降。
  2. 事件触发频率高:在高并发场景下,如果事件频繁触发,每次触发都要执行所有注册的监听器,会导致 CPU 使用率飙升。因为频繁的函数调用和上下文切换会带来额外的开销。
  3. 监听器执行时间长:如果某个或多个监听器的执行逻辑复杂,耗时较长,会阻塞事件循环,导致后续的事件处理延迟,影响整个应用的响应性能。

优化方案

  1. 减少不必要的监听器
    • 在不需要监听某些事件时,及时移除监听器。例如,当某个特定任务完成后,与之相关的临时监听器就可以取消注册。
    • 可以通过使用一次性监听器(once 方法),在事件第一次触发后自动移除监听器,适用于只需要响应一次的场景。
  2. 优化监听器逻辑
    • 将复杂的监听器逻辑拆分成多个小的、独立的函数,尽量减少单个监听器的执行时间,避免阻塞事件循环。
    • 对于一些耗时较长的操作,可以使用异步操作(如 setTimeoutPromiseasync/await),让事件循环可以继续处理其他事件。
  3. 批量处理事件
    • 对于频繁触发的事件,可以采用批量处理的方式。例如,设置一个定时器,在一定时间间隔内收集事件,然后一次性处理这些事件,而不是每次事件触发都立即处理。

高效高并发任务调度系统示例

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 触发相应的事件,其他模块可以监听这些事件来进行相应的处理。这样可以实现一个高效的高并发任务调度系统。