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>
5.0 KiB
5.0 KiB
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-managerspec:反映新的多匹配语义
调度器稳定性修复
- 新增
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、hasAnyEnabledIntervalinternal/polling/initializer.go:initBatchinterval 选取逻辑;日志级别 Warn→Error;Pipeline 逐条错误检查internal/polling/lifecycle_service.go:enqueueCardinterval 选取逻辑,移除getEnabledTaskTypesinternal/polling/scheduler.go:新增顶层 panic recovery、心跳写入、Init 守卫;新增initializer字段internal/task/polling_base.go:requeueCardinterval 选取逻辑(稳态路径,每次轮询任务完成后执行)internal/task/polling_utils.go:移除getIntervalByTaskType(被MergedTaskIntervals替代)pkg/constants/redis.go:新增RedisPollingSchedulerHeartbeatKey()
- 不受影响:API 接口、DB schema、Asynq 任务提交逻辑(
queue_manager.go)、handler 层 - 预期收益:
- 配置系统按预期工作,id=28(suspended + card_status_check_interval=600)能正确对停机卡生效
- 调度器故障可被告警规则(检查心跳 key)及时发现,不再静默失效
- 启动期不再产生无效轮询噪音
- 卡初始化失败可见,不再静默丢失