5.1 KiB
Context
见 proposal.md。当前到期处理在事务提交后投递异步套餐激活任务,同时以 goroutine 发起停机检查;两条路径没有共同顺序或共享状态结论。生产中已观察到新套餐激活后 1.9 至 15.7 秒仍写入 no_package 停机。
现有 ActivationService 已负责待生效主套餐激活及激活后的复机回调;StopResumeService 已集中停复机条件。保留这两个职责边界,不增加新的状态表或外部依赖。
Goals / Non-Goals
Goals:
- 同一载体到期接续期间不以过时权益快照停机。
- 没有后续可生效套餐时保留现有停机语义。
- 激活投递或执行暂时失败时宁可交由既有恢复/轮询兜底,不误判为无套餐。
- 给维护者提供基于当前事实的只读候选筛选与受控补偿流程。
Non-Goals:
- 不新增自动批量复机、管理接口或数据库迁移。
- 不把 Redis/Asynq 运行排障改造成业务状态机。
- 不改变手动停机、运营商风险停机或实名策略语义。
Decisions
1. 将停机决策后移到套餐激活任务的最终重新评估
到期处理找到后续待生效主套餐时,只投递激活任务,不并发启动停机检查。激活任务完成后以该载体的最新套餐状态调用现有停机评估:成功激活时不触发无套餐停机;队首套餐因实名等业务条件不能激活时,才按当前事实评估停机。
这样复用现有激活锁、套餐条件和停复机服务,不让两个异步流程各自读取一次状态。
备选方案:在到期处理内同步完成套餐激活。未采用,因为到期扫描单轮最多处理大量套餐,同步串行激活会放大调度 Worker 的数据库和 Redis 锁持有时间。
2. 激活投递失败不是无套餐结论
仅在明确不存在后续主套餐时,允许到期处理立即发起停机评估。存在后续套餐但投递失败时记录错误并保留当前网络状态,由既有孤儿套餐恢复扫描和套餐轮询重试;不得回退到并发停机。
备选方案:投递失败立即停机。未采用,因为它重新引入“接续结果未知被当作无套餐”的错误路径。
3. 存量恢复与代码修复分离
代码发布后,维护者用只读查询筛选“有效主套餐、未耗尽、实名满足、no_package 停机”的候选卡;每张或受控小批次先查询运营商实时状态,再调用既有复机能力。补偿不直接更新数据库,也不自动对全部候选执行。
备选方案:发布时自动批量复机全部候选。未采用,因为本地状态不足以代替运营商风险、销户和人工业务判断。
4. 轮询只作兜底并单独核验运行覆盖
停机卡配置的 polling:package 仍是后备恢复机制,不能作为避免竞态的主路径。维护者分别核验调度 Worker 心跳、Redis 分片队列深度、Asynq 队列积压与消费者吞吐;这些运行指标只用于定位未补偿原因。
5. 卡状态轮询仅补齐缺失的套餐任务
生产日志确认存在卡状态和流量任务仍持续执行、但同一卡的 polling:package 队列项缺失的情形。卡状态轮询成功且未命中风险停机后,按数据库最新卡状态重新匹配轮询配置;若该卡应参与套餐检查,则以 Redis ZADD NX 仅在对应分片 Sorted Set 不存在该卡时立即补入套餐任务。已存在任务不得覆盖其 score,也不新增重复任务。
该做法不将卡状态轮询变为停复机决策路径:它只修复缺失的套餐调度项,最终仍由既有 PollingPackageHandler → EvaluateAndAct 判断权益、实名、停机原因和运营商结果。
Risks / Trade-offs
- [激活任务投递失败时卡暂不因套餐到期自动停机] → 保留孤儿恢复扫描和套餐轮询;记录失败并在运行排障中核验任务恢复。
- [激活任务内增加最终停机评估] → 保持幂等条件和现有 Redis 锁;同一载体状态变化必须重读数据库。
- [存量候选包含运营商侧不可复机卡] → 补偿前逐卡或受控小批查询运营商状态,拒绝风险停机、销户或其他非本地套餐原因。
- [轮询吞吐不足延迟兜底] → 发布前后分别记录队列深度、消费速率和最近检查时间分布,不以单次数据库字段判断 Worker 健康。
- [卡状态轮询补齐套餐任务] → 仅使用
ZADD NX补入缺失成员,不改写已有任务的执行时间;最终复机仍受既有权益、实名、停机原因和运营商校验约束。
Migration Plan
- 在隔离环境验证“旧套餐到期 + 后续套餐可激活”不会产生停机调用,及“无后续/不可激活”仍进入停机评估。
- 按生产运行说明发布 Worker;不需要数据库迁移。
- 维护者观察一轮套餐到期处理、激活任务与轮询队列,确认没有新的“有效套餐 +
no_package停机”记录。 - 使用只读筛选得到存量候选,按受控批次核验运营商状态后调用既有复机;失败项保留失败原因并人工处理。
- 若发布后出现意外停机语义,回滚 Worker 二进制;存量补偿不得直接写库。