fix(db): 修复数据库断连后无法自动恢复的问题
- 添加连接超时配置防止无限等待 - 捕获连接池错误避免进程崩溃 - 缩短心跳检测间隔至5秒
This commit is contained in:
29
docs/archive/incident_report_20260210.md
Normal file
29
docs/archive/incident_report_20260210.md
Normal file
@@ -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. 结论
|
||||
系统现在具备了对数据库“突然断电/断网”场景的自动感知与快速恢复能力。
|
||||
@@ -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);
|
||||
// 不抛出,让应用层通过心跳检测发现问题
|
||||
});
|
||||
|
||||
// 测试连接
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user