在 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也会被递减,从而让下一个排队任务得以继续。 - 资源管理:在微服务环境中,并发限制应计算为 总数据库连接数 / 服务实例数量。
💡 有疑问吗?在评论区留下吧!