From 58c38807320fd3a98e18a63a7fb781df059a81bd Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Fri, 27 Feb 2026 08:56:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(db):=20=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=96=AD=E8=BF=9E=E5=90=8E=E6=97=A0=E6=B3=95=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=81=A2=E5=A4=8D=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加连接超时配置防止无限等待 - 捕获连接池错误避免进程崩溃 - 缩短心跳检测间隔至5秒 --- docs/archive/incident_report_20260210.md | 29 ++++++++++++++++++++++++ src/db/databaseManager.js | 7 ++++++ src/processor/heartbeatProcessor.js | 4 ++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 docs/archive/incident_report_20260210.md diff --git a/docs/archive/incident_report_20260210.md b/docs/archive/incident_report_20260210.md new file mode 100644 index 0000000..aaa1c76 --- /dev/null +++ b/docs/archive/incident_report_20260210.md @@ -0,0 +1,29 @@ +# 事故报告:数据库断电后服务无法自动恢复 +日期:2026-02-10 + +## 1. 问题描述 +用户反馈 PostgreSQL 数据库因断电突然离线,恢复后 `Web_BLS_Heartbeat_Server` 服务没有自动重连并恢复写入,处于"毫无反应"的状态。 + +## 2. 原因分析 +经过代码审查,发现以下潜在原因: +1. **连接超时缺失**:`pg` 连接池默认配置中 `connectionTimeoutMillis` 未设置(默认为0,即无限等待)。当数据库主机突然断电(非正常关闭 TCP 连接)时,客户端 Socket 可能处于半打开状态,导致 `pool.connect()` 或 `pool.query()` 无限挂起,既不成功也不报错。 +2. **重试间隔过长**:原有的 `HeartbeatProcessor` 离线检测机制设定为 60 秒检查一次,对于需要快速恢复的场景响应过慢。 +3. **未捕获的 Pool 错误**:`DatabaseManager` 未监听 `pool.on('error')`,虽然这通常导致进程崩溃而非挂起,但也属于稳定性隐患。 + +## 3. 修复措施 +### 3.1 DatabaseManager (`src/db/databaseManager.js`) +- **新增配置**:添加 `connectionTimeoutMillis: 5000`。这强制任何获取连接的操作在 5 秒内必须完成,否则抛出 `ETIMEDOUT` 错误。这确保了应用层能感知到“网络不通”,而不是无限等待。 +- **错误监听**:添加 `pool.on('error')` 处理程序,防止后台连接断开导致 Node.js 进程意外退出。 + +### 3.2 HeartbeatProcessor (`src/processor/heartbeatProcessor.js`) +- **缩短检测间隔**:将数据库离线后的健康检查间隔从 60 秒缩短为 5 秒。这意味着数据库恢复后,服务最快在 5 秒内即可恢复消费。 + +## 4. 验证结果 +编写了模拟测试脚本 `scripts/test_reconnect_logic.js` 进行验证: +1. **模拟断连**:模拟 DB 抛出 `ETIMEDOUT` 错误。 + - 结果:Consumer 成功暂停,进入离线模式。 +2. **模拟恢复**:5秒后模拟 DB 连接恢复。 + - 结果:Consumer 自动恢复(Resume),积压的数据成功写入。 + +## 5. 结论 +系统现在具备了对数据库“突然断电/断网”场景的自动感知与快速恢复能力。 diff --git a/src/db/databaseManager.js b/src/db/databaseManager.js index ee4664d..a5994ac 100644 --- a/src/db/databaseManager.js +++ b/src/db/databaseManager.js @@ -20,6 +20,13 @@ class DatabaseManager { database: this.config.database, max: this.config.maxConnections, idleTimeoutMillis: this.config.idleTimeoutMillis, + connectionTimeoutMillis: 5000, // 5秒连接超时,防止断网时无限等待 + }); + + // 监听连接池错误,防止后端断开导致进程崩溃 + this.pool.on('error', (err, client) => { + console.error('[db] 发生未捕获的连接池错误:', err); + // 不抛出,让应用层通过心跳检测发现问题 }); // 测试连接 diff --git a/src/processor/heartbeatProcessor.js b/src/processor/heartbeatProcessor.js index d4ff627..6d64ebb 100644 --- a/src/processor/heartbeatProcessor.js +++ b/src/processor/heartbeatProcessor.js @@ -327,14 +327,14 @@ class HeartbeatProcessor { this.onDbOnline?.(); this.processBatch(); } else { - console.warn('数据库仍离线,1分钟后重试...'); + console.warn('数据库仍离线,5秒后重试...'); this._scheduleDbCheck(); } } catch (err) { console.warn('数据库检查异常:', err); this._scheduleDbCheck(); } - }, 60000); + }, 5000); } _emitDbWriteError(error, rawData) {