在 Node.js 中实现舱壁模式

发布: (2025年12月16日 GMT+8 19:41)
4 min read
原文: Dev.to

Source: Dev.to

系统弹性简介

在分布式系统或微服务架构中,单个故障组件可能会导致连锁反应,进而使整个应用崩溃。这常见于数据库或下游服务变慢时。进入的请求会不断堆积,消耗内存和 CPU 周期,直至 Node.js 事件循环耗尽。

Bulkhead(舱壁)模式是一种结构化设计,用于隔离关键资源。通过限制能够访问特定资源(如数据库)的并发操作数量,我们可以确保流量激增或数据库变慢时不会耗尽所有服务器资源。

信号量(semaphore)是实现 Bulkhead 的同步原语。与一次只能有一个任务执行的互斥锁(mutex)不同,信号量允许 N 个并发任务。

在 Node.js 环境中,我们使用信号量来管理:

  • 计数器:跟踪当前正在进行的异步操作数量。
  • 队列:存放在并发限制已达时到达的任务的 Promise “resolve” 函数。
  • 等待(P)/信号(V):分别请求一个槽位或释放一个槽位的操作。

该类需要 concurrencyLimit 来定义最大同时操作数,并通过 queueLimit 防止无限等待队列导致内存耗尽。

Bulkhead 实现

class Bulkhead {
  constructor(concurrencyLimit, queueLimit = 100) {
    this.concurrencyLimit = concurrencyLimit;
    this.queueLimit = queueLimit;
    this.activeCount = 0;
    this.queue = [];
  }

  async run(task) {
    // Admission Control and Wait Logic
    if (this.activeCount >= this.concurrencyLimit) {
      if (this.queue.length >= this.queueLimit) {
        throw new Error("Bulkhead capacity exceeded: Server Busy");
      }

      await new Promise((resolve) => {
        this.queue.push(resolve);
      });
    }

    this.activeCount++;

    try {
      // Execution of the asynchronous task
      return await task();
    } finally {
      // Release Logic
      this.activeCount--;
      if (this.queue.length > 0) {
        const nextInLine = this.queue.shift();
        nextInLine();
      }
    }
  }
}

在 Mongoose 中使用 Bulkhead

const dbBulkhead = new Bulkhead(5, 10);

app.get('/data', async (req, res) => {
  try {
    const result = await dbBulkhead.run(() => User.find().lean());
    res.json(result);
  } catch (error) {
    res.status(503).json({ message: error.message });
  }
});

数据库调用被包装在 run 方法中。即使有 1,000 个请求同时命中该 API 端点,也只有受控数量的请求真正访问数据库。

关键技术要点

  • 快速失败(Admission Control):通过检查 queue.length,我们立即拒绝请求(HTTP 503),而不是让它们挂起并消耗 RAM。
  • 错误隔离finally 块保证即使数据库查询失败,activeCount 也会被递减,从而让下一个排队任务得以继续。
  • 资源管理:在微服务环境中,并发限制应计算为 总数据库连接数 / 服务实例数量

💡 有疑问吗?在评论区留下吧!

Back to Blog

相关文章

阅读更多 »