All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m27s
核心变更: - MatchConfig 改为 MatchConfigs,返回所有匹配配置 - MergedTaskIntervals 按 task type 合并各配置,选取最高优先级(非 nil 且最小 priority 值) - hasAnyEnabledInterval 过滤所有 interval 均为 NULL 的配置 - calcInitialDelay 重构为纯函数,接收 interval 参数 - 移除 getEnabledTaskTypes 和 getIntervalByTaskType(被 MergedTaskIntervals 替代) - scheduler.go 新增心跳 key + 顶层 panic recovery + Init 完成守卫 - initializer.go 批量失败日志升级为 Error,逐条检查 Pipeline 命令错误 - 数据迁移:禁用 id=29 的轮询配置(所有 interval 均为 NULL) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
57 lines
5.0 KiB
Markdown
57 lines
5.0 KiB
Markdown
## Why
|
||
|
||
本次变更包含两个独立但相关的修复,均涉及轮询系统的核心可靠性:
|
||
|
||
**问题一(配置多匹配)**:当前 `PollingConfigManager.MatchConfig(card)` 的实现是"第一个匹配的配置直接返回,后续配置全部忽略"。这个设计在配置优先级体系下存在致命缺陷:**高 priority 的 catch-all 配置(如 id=29,priority=1,所有 interval 全为 NULL)会匹配所有卡,直接跳过更具体的配置(如 id=28,priority=25,card_status_check_interval=600),导致整个轮询系统失效**。
|
||
|
||
**问题二(调度器稳定性)**:轮询调度器(`Scheduler`)的主循环 `scheduleLoop` 没有顶层 panic recovery 和心跳上报机制。一旦 `processActivationTasks` 等路径发生 panic,整个调度 goroutine 静默终止——所有分片队列持续积压,但 Worker 进程看起来正常,无任何告警。同时,初始化完成前调度器无谓地空轮询;`initBatch` 失败时仅记录 Warn 掩盖了卡初始化缺失的严重性;Pipeline 执行只检查整体 error 而不逐条检查 cmder,部分卡初始化失败会静默丢失。
|
||
|
||
本次修复将匹配逻辑从"独占式单匹配"改为"组合式多匹配",并同步修复上述调度器稳定性问题。
|
||
|
||
## What Changes
|
||
|
||
### 配置多匹配修复
|
||
|
||
- **修改 `MatchConfig` → `MatchConfigs`**:返回所有满足条件的配置(按 priority ASC 排序),而非第一个
|
||
- **修改 `matchConfigConditions`**:跳过所有 interval 均为 NULL 的配置(这些配置不应参与轮询匹配)
|
||
- **修改 `initBatch`**:对每种 task type,从所有匹配配置中选取最高优先级的非 NULL interval
|
||
- **修改 `enqueueCard`**(`lifecycle_service.go`):同上,合并多配置的 interval
|
||
- **修改 `getEnabledTaskTypes` → 移除**:不再需要,`MergedTaskIntervals` 已包含启用的 task type 列表
|
||
- **重构 `calcInitialDelay`**:修改函数签名接受 `interval int` 参数,移除对 `*model.PollingConfig` 的依赖(因为新逻辑中 interval 来源是 `MergedTaskIntervals` 合并结果,不再属于单一 config)
|
||
- **修改 `requeueCard`**(`internal/task/polling_base.go`):同上,使用 `MergedTaskIntervals` 替代 `MatchConfig` + `getIntervalByTaskType`
|
||
- **移除 `getIntervalByTaskType`**(`internal/task/polling_utils.go`):`MergedTaskIntervals` 已提供按 task type 查 interval 的能力,该辅助函数不再需要
|
||
- **更新 `polling-config-manager` spec**:反映新的多匹配语义
|
||
|
||
### 调度器稳定性修复
|
||
|
||
- **新增 `scheduleLoop` 顶层 panic recovery**:主调度 goroutine 崩溃时记录 Error 日志,防止调度静默停止
|
||
- **新增 Scheduler 心跳机制**:每次 tick 写入 `polling:scheduler:heartbeat`(TTL=2×ScheduleInterval),供告警规则检测调度器存活
|
||
- **新增 Init 完成守卫**:`processShardSchedule` 首先检查 `initializer.IsCompleted()`,Init 未完成时跳过出队,消除启动期无效轮询噪音
|
||
- **`initBatch` 失败日志 Warn→Error**:批量初始化失败意味着部分卡未进入轮询队列,应使用 Error 级别
|
||
- **Pipeline 错误逐条检查**:`pipe.Exec()` 后遍历 `[]redis.Cmder` 逐条检查 `cmd.Err()`,记录具体失败的 ZADD 命令,防止部分卡初始化失败静默丢失
|
||
- **新增 `RedisPollingSchedulerHeartbeatKey()`**(`pkg/constants/redis.go`):心跳 Key 统一管理
|
||
|
||
## Capabilities
|
||
|
||
### Modified Capabilities
|
||
|
||
- `polling-config-manager`:原有的"第一个匹配独占"语义改为"所有匹配配置 + 按 task type 选取最高优先级 interval"。这是**需求级别变更**(不仅是实现细节),需要更新 spec 中的 MatchConfig 描述和 Scenario。
|
||
- `polling-scheduler`:新增心跳上报、Init 完成守卫、顶层 panic recovery,调度器可观测性和稳定性提升。
|
||
|
||
## Impact
|
||
|
||
- **受影响文件**:
|
||
- `internal/polling/config_manager.go`:新增 `MatchConfigs`、`MergedTaskIntervals`、`hasAnyEnabledInterval`
|
||
- `internal/polling/initializer.go`:`initBatch` interval 选取逻辑;日志级别 Warn→Error;Pipeline 逐条错误检查
|
||
- `internal/polling/lifecycle_service.go`:`enqueueCard` interval 选取逻辑,移除 `getEnabledTaskTypes`
|
||
- `internal/polling/scheduler.go`:新增顶层 panic recovery、心跳写入、Init 守卫;新增 `initializer` 字段
|
||
- `internal/task/polling_base.go`:`requeueCard` interval 选取逻辑(**稳态路径,每次轮询任务完成后执行**)
|
||
- `internal/task/polling_utils.go`:移除 `getIntervalByTaskType`(被 `MergedTaskIntervals` 替代)
|
||
- `pkg/constants/redis.go`:新增 `RedisPollingSchedulerHeartbeatKey()`
|
||
- **不受影响**:API 接口、DB schema、Asynq 任务提交逻辑(`queue_manager.go`)、handler 层
|
||
- **预期收益**:
|
||
1. 配置系统按预期工作,id=28(suspended + card_status_check_interval=600)能正确对停机卡生效
|
||
2. 调度器故障可被告警规则(检查心跳 key)及时发现,不再静默失效
|
||
3. 启动期不再产生无效轮询噪音
|
||
4. 卡初始化失败可见,不再静默丢失
|