## 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. 卡初始化失败可见,不再静默丢失