|
|
@@ -0,0 +1,340 @@
|
|
|
+# S8 数据库处理优化方案
|
|
|
+
|
|
|
+> 文档定位:S8 异常协同模块数据库写入、调度任务与运行稳定性优化方案
|
|
|
+> 创建日期:2026-05-28
|
|
|
+> 触发背景:超级管理员登录在 `CreateToken` 阶段超时,排查发现 MySQL 大量事务卡在 `waiting for handler commit`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、问题背景
|
|
|
+
|
|
|
+2026-05-28 凌晨,超级管理员登录失败。后端日志显示账号密码校验已经通过,失败点发生在登录成功后的令牌创建阶段:
|
|
|
+
|
|
|
+- `SysAuthService.Login` 调用 `CreateToken`
|
|
|
+- `CreateToken` 更新 `SysUser.LastLoginTime`、`LastLoginIp` 等字段
|
|
|
+- MySQL 返回 `The Command Timeout expired before the operation completed`
|
|
|
+
|
|
|
+数据库侧同时出现大量 `waiting for handler commit`,涉及 `SysUser`、`SysJobTriggerRecord`、`SysJobDetail`、`ado_s8_watch_rule`、`ado_s8_notification_log`、`SysLogOp`、`SysLogEx` 等表。MySQL 还出现 `The table '/tmp/#sql...' is full`,说明数据库服务器临时目录、磁盘或提交刷盘链路已进入异常状态。
|
|
|
+
|
|
|
+本次故障不是超级管理员账号逻辑、审批流候选用户接口或前端登录逻辑导致;直接根因是数据库提交阶段阻塞,S8/审批/调度类任务的高频写入进一步放大了数据库压力。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、现场证据
|
|
|
+
|
|
|
+### 2.1 登录失败点
|
|
|
+
|
|
|
+登录请求中,超级管理员账号已进入 `CreateToken`,异常栈落在更新用户登录信息:
|
|
|
+
|
|
|
+```text
|
|
|
+Admin.NET.Core.Service.SysAuthService.CreateToken(...)
|
|
|
+UPDATE `SysUser`
|
|
|
+SET `LastLoginIp` = ..., `LastLoginTime` = ...
|
|
|
+WHERE `Id` = 1300000000101
|
|
|
+
|
|
|
+MySqlConnector.MySqlException:
|
|
|
+The Command Timeout expired before the operation completed.
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 数据库提交堆积
|
|
|
+
|
|
|
+`SHOW FULL PROCESSLIST` 显示多个事务长时间停留在:
|
|
|
+
|
|
|
+```text
|
|
|
+State: waiting for handler commit
|
|
|
+```
|
|
|
+
|
|
|
+典型 SQL 包括:
|
|
|
+
|
|
|
+- `UPDATE SysUser SET LastLoginTime = ... WHERE Id = 1300000000101`
|
|
|
+- `INSERT INTO SysJobTriggerRecord (...)`
|
|
|
+- `UPDATE SysJobDetail SET ... WHERE JobId = ...`
|
|
|
+- `UPDATE ado_s8_watch_rule SET lock_token = ..., lock_until = ...`
|
|
|
+- `INSERT INTO ado_s8_notification_log (...)`
|
|
|
+- `INSERT INTO SysLogOp / SysLogEx (...)`
|
|
|
+
|
|
|
+### 2.3 多实例同时写同一库
|
|
|
+
|
|
|
+数据库进程中可见至少两个应用来源同时连接 `aidopdev`:
|
|
|
+
|
|
|
+- `49.65.129.55`
|
|
|
+- `39.105.125.212`
|
|
|
+
|
|
|
+这意味着多个后端实例可能同时注册并执行定时任务。`Concurrent = false` 只能限制单进程内并发,不能阻止多实例重复调度。
|
|
|
+
|
|
|
+### 2.4 MySQL 临时空间异常
|
|
|
+
|
|
|
+查询进程统计时 MySQL 返回:
|
|
|
+
|
|
|
+```text
|
|
|
+The table '/tmp/#sql...' is full
|
|
|
+```
|
|
|
+
|
|
|
+该现象通常指向数据库服务器 `/tmp`、MySQL 临时目录、根盘、数据盘或临时表空间不足,也可能与 IO 阻塞叠加。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、S8 当前不合理点
|
|
|
+
|
|
|
+### 3.1 缺少环境级调度总开关
|
|
|
+
|
|
|
+S8 相关 Job 通过 Furion Schedule 注解注册:
|
|
|
+
|
|
|
+- `job_s8_watch_scheduler`
|
|
|
+- `job_s8_timeout_auto_escalation`
|
|
|
+- `job_s8_active_flow_stuck_scan`
|
|
|
+
|
|
|
+只要服务启动并启用调度,这些 Job 就可能运行。开发机、测试机、生产机如果连接同一个库,就会重复写调度表和业务表。
|
|
|
+
|
|
|
+### 3.2 主调度 1 分钟 tick 偏频繁
|
|
|
+
|
|
|
+`S8WatchSchedulerJob` 当前固定 1 分钟触发:
|
|
|
+
|
|
|
+```text
|
|
|
+trigger_s8_watch_scheduler: 60000 ms
|
|
|
+```
|
|
|
+
|
|
|
+虽然单条规则有 `poll_interval_seconds` 和 `next_run_at` 控制,但 Job 每分钟仍会发生:
|
|
|
+
|
|
|
+- Furion 调度触发记录写入
|
|
|
+- 过期 lease 清理查询/更新
|
|
|
+- 到期规则查询
|
|
|
+- 运行日志输出
|
|
|
+
|
|
|
+在多实例部署下,该成本会线性放大。
|
|
|
+
|
|
|
+### 3.3 Furion 调度记录写入偏高
|
|
|
+
|
|
|
+每次 Job 执行都会写入 `SysJobTriggerRecord`。S8 主调度 1 分钟一次,另外还有每 5 分钟执行的 S8 超时升级、ActiveFlow 卡死扫描、审批流超时任务。多实例同时运行时,`SysJobTriggerRecord` 写入频率明显升高。
|
|
|
+
|
|
|
+### 3.4 DetectionLog 记录粒度偏细
|
|
|
+
|
|
|
+S8 每条规则运行会写 `AdoS8DetectionLog`,包括:
|
|
|
+
|
|
|
+- `NO_HIT`
|
|
|
+- `REFRESHED`
|
|
|
+- `CREATED`
|
|
|
+- `RECOVERED`
|
|
|
+- `EVALUATE_FAILED`
|
|
|
+
|
|
|
+其中 `NO_HIT` 和持续 `REFRESHED` 在长期稳定状态下价值较低,但会持续写库、持续增长。
|
|
|
+
|
|
|
+### 3.5 ActiveFlow 孤儿扫描去重依赖 Payload
|
|
|
+
|
|
|
+`S8ActiveFlowWatchService` 在孤儿流程扫描中读取通知日志 `Payload` 并解析 `flowInstanceId` 做去重。`Payload` 是大 JSON 文本,难以利用索引,数据量增长后扫描成本会持续上升。
|
|
|
+
|
|
|
+### 3.6 规则 SQL 缺少统一执行保护
|
|
|
+
|
|
|
+规则 evaluator 通过数据源 SQL 拉取结果。如果规则 SQL 没有时间窗口、分页或限制行数,容易出现一次性全量扫描。当前需要补充统一超时、最大返回行数和规则 SQL 审核约束。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、优化目标
|
|
|
+
|
|
|
+1. **恢复稳定性**:避免 S8/审批调度写入拖垮登录、菜单、用户信息等基础能力。
|
|
|
+2. **控制写入频率**:减少空跑、重复跑和低价值日志写入。
|
|
|
+3. **支持多环境隔离**:开发、测试、生产连接同库时,不默认重复执行调度。
|
|
|
+4. **增强可观测性**:保留关键运行证据,但避免日志表无限增长。
|
|
|
+5. **降低数据库依赖风险**:规则 SQL、通知扫描、调度记录均有明确上限与归档策略。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、优化方案
|
|
|
+
|
|
|
+### 5.1 增加调度总开关
|
|
|
+
|
|
|
+新增配置建议:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "Scheduler": {
|
|
|
+ "Enabled": false,
|
|
|
+ "OwnerInstance": "prod-scheduler-01"
|
|
|
+ },
|
|
|
+ "S8": {
|
|
|
+ "Scheduler": {
|
|
|
+ "Enabled": false
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+建议策略:
|
|
|
+
|
|
|
+- 开发环境默认 `false`
|
|
|
+- 测试/生产只允许一个服务实例为 `true`
|
|
|
+- 普通 Web 实例只提供 API,不运行后台调度
|
|
|
+- 调度实例单独部署、单独监控
|
|
|
+
|
|
|
+### 5.2 S8 主调度频率配置化
|
|
|
+
|
|
|
+将 `S8WatchSchedulerJob.IntervalMs` 从固定常量调整为配置项。
|
|
|
+
|
|
|
+建议默认:
|
|
|
+
|
|
|
+- 开发环境:关闭
|
|
|
+- 测试环境:5 分钟
|
|
|
+- 生产环境:1 至 5 分钟,由规则规模决定
|
|
|
+
|
|
|
+规则本身已经有 `poll_interval_seconds` 和 `next_run_at`,因此 Job tick 不必长期固定为 1 分钟。
|
|
|
+
|
|
|
+### 5.3 降低空跑写入
|
|
|
+
|
|
|
+优化方向:
|
|
|
+
|
|
|
+- `picked = 0` 的 tick 不写业务侧检测日志。
|
|
|
+- Furion 触发记录保留系统默认行为,但降低 Job 频率。
|
|
|
+- `S8WatchSchedulerJob` 只在有 picked、failed、created、refreshed、leaseReleased 时输出详细日志。
|
|
|
+
|
|
|
+### 5.4 DetectionLog 分级记录
|
|
|
+
|
|
|
+建议调整:
|
|
|
+
|
|
|
+- `CREATED`、`RECOVERED`、`EVALUATE_FAILED`:必须保留明细。
|
|
|
+- `REFRESHED`:可按规则/对象限频记录,例如同一 dedupKey 10 分钟最多一条。
|
|
|
+- `NO_HIT`:不再每轮写明细,改为更新 `AdoS8RuleDetectionState` 或规则 last 状态。
|
|
|
+- 增加清理策略:保留 30 至 90 天明细,历史数据归档或汇总。
|
|
|
+
|
|
|
+### 5.5 ActiveFlow 告警去重结构化
|
|
|
+
|
|
|
+新增结构化字段或独立告警表,避免从 `Payload` 解析:
|
|
|
+
|
|
|
+- `channel`
|
|
|
+- `exception_id`
|
|
|
+- `flow_instance_id`
|
|
|
+- `biz_type`
|
|
|
+- `biz_id`
|
|
|
+- `created_at`
|
|
|
+
|
|
|
+建立索引:
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE INDEX idx_s8_notify_channel_flow_time
|
|
|
+ON ado_s8_notification_log(channel, flow_instance_id, created_at);
|
|
|
+```
|
|
|
+
|
|
|
+如果不改通知日志表,也可新增 `ado_s8_alert_dedup` 专用表承载去重键。
|
|
|
+
|
|
|
+### 5.6 规则 SQL 执行保护
|
|
|
+
|
|
|
+为所有 SQL evaluator 增加约束:
|
|
|
+
|
|
|
+- 设置 CommandTimeout,例如 10 至 30 秒。
|
|
|
+- 规则 SQL 必须包含时间窗口或业务状态过滤。
|
|
|
+- 对结果行数设置上限,例如 500 或 1000。
|
|
|
+- 超限时写 `EVALUATE_FAILED`,并累计失败次数触发自动暂停。
|
|
|
+- SQL 配置评审时检查是否命中索引。
|
|
|
+
|
|
|
+### 5.7 调度表清理与归档
|
|
|
+
|
|
|
+重点处理:
|
|
|
+
|
|
|
+- `SysJobTriggerRecord`
|
|
|
+- `AdoS8DetectionLog`
|
|
|
+- `ado_s8_notification_log`
|
|
|
+- `SysLogOp`
|
|
|
+- `SysLogEx`
|
|
|
+
|
|
|
+建议:
|
|
|
+
|
|
|
+- 调度记录保留 7 至 30 天。
|
|
|
+- 检测明细保留 30 至 90 天。
|
|
|
+- 系统操作日志按现有审计要求保留,超过期限归档。
|
|
|
+- 清理任务必须错峰运行,避免与 S8 主调度、MDP 同步同一时间段执行。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、实施计划
|
|
|
+
|
|
|
+### 阶段一:止血与配置隔离
|
|
|
+
|
|
|
+目标:先避免再次拖垮基础登录能力。
|
|
|
+
|
|
|
+任务:
|
|
|
+
|
|
|
+- 停止多余后端实例的调度能力。
|
|
|
+- 增加调度总开关,开发环境默认关闭。
|
|
|
+- 明确只允许一个调度实例连接 `aidopdev` 执行 Job。
|
|
|
+- 将 S8 主调度默认频率调整为 5 分钟或配置化。
|
|
|
+
|
|
|
+验收:
|
|
|
+
|
|
|
+- `SHOW FULL PROCESSLIST` 中不再持续出现大量 S8/Job 相关 `waiting for handler commit`。
|
|
|
+- 超级管理员登录稳定在 3 秒以内。
|
|
|
+- `SysJobTriggerRecord` 写入速率明显下降。
|
|
|
+
|
|
|
+### 阶段二:降低 S8 写入量
|
|
|
+
|
|
|
+目标:减少低价值日志和空跑写入。
|
|
|
+
|
|
|
+任务:
|
|
|
+
|
|
|
+- 调整 `AdoS8DetectionLog` 记录策略。
|
|
|
+- `NO_HIT` 改为汇总状态,不每轮插入明细。
|
|
|
+- `REFRESHED` 增加限频或合并策略。
|
|
|
+- 增加日志清理/归档脚本。
|
|
|
+
|
|
|
+验收:
|
|
|
+
|
|
|
+- 无命中规则运行时,`AdoS8DetectionLog` 不再线性增长。
|
|
|
+- 长期重复命中的对象不会高频写刷新日志。
|
|
|
+- S8 功能看板仍能展示关键创建、恢复、失败证据。
|
|
|
+
|
|
|
+### 阶段三:扫描与 SQL 执行优化
|
|
|
+
|
|
|
+目标:降低单次规则或扫描拖垮数据库的风险。
|
|
|
+
|
|
|
+任务:
|
|
|
+
|
|
|
+- ActiveFlow 孤儿扫描去重字段结构化。
|
|
|
+- 规则 SQL 增加超时、行数上限和索引评审。
|
|
|
+- 对慢规则记录运行耗时并自动暂停。
|
|
|
+
|
|
|
+验收:
|
|
|
+
|
|
|
+- 单条规则运行超时可被捕获并记录,不拖垮整个 tick。
|
|
|
+- ActiveFlow 扫描在数据量增长后仍保持可控耗时。
|
|
|
+- 慢规则连续失败后自动暂停,避免持续冲击数据库。
|
|
|
+
|
|
|
+### 阶段四:数据库侧长期治理
|
|
|
+
|
|
|
+目标:避免数据库环境自身成为单点风险。
|
|
|
+
|
|
|
+任务:
|
|
|
+
|
|
|
+- 监控 `/tmp`、MySQL 数据盘、binlog、redo、undo 空间。
|
|
|
+- 监控 `Threads_running`、`waiting for handler commit`、行锁等待、慢 SQL。
|
|
|
+- 对开发库评估 `sync_binlog`、`innodb_flush_log_at_trx_commit` 配置;生产库需按可靠性要求谨慎调整。
|
|
|
+- 对高频日志表建立保留策略和归档策略。
|
|
|
+
|
|
|
+验收:
|
|
|
+
|
|
|
+- 磁盘使用率、临时目录使用率有告警。
|
|
|
+- 提交等待和连接堆积能在故障前被发现。
|
|
|
+- 调度类表增长有可预测上限。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、应急处理流程
|
|
|
+
|
|
|
+当再次出现登录超时、菜单加载超时或大量 SQL 提交等待时,按以下顺序处理:
|
|
|
+
|
|
|
+1. 停止多余应用实例,保留一个调度实例。
|
|
|
+2. 检查数据库服务器磁盘、inode、`/tmp` 和 MySQL 数据目录。
|
|
|
+3. 查看 `SHOW FULL PROCESSLIST`,确认是否大量 `waiting for handler commit`。
|
|
|
+4. 优先重启 MySQL 或谨慎 kill 长时间卡住的应用连接。
|
|
|
+5. 登录超级管理员验证基础链路。
|
|
|
+6. 再逐步恢复 S8/审批/MDP 定时任务。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 八、后续决策点
|
|
|
+
|
|
|
+需要负责人确认:
|
|
|
+
|
|
|
+- S8 调度实例是否单独部署。
|
|
|
+- 各环境默认是否禁用调度。
|
|
|
+- DetectionLog 保留期限。
|
|
|
+- `SysJobTriggerRecord` 清理策略。
|
|
|
+- 规则 SQL 最大返回行数和超时阈值。
|
|
|
+- 是否允许开发库调整 MySQL 刷盘参数以提升可用性。
|