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) {