All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m33s
- 新增网关限流:按本系统运营商账户(卡 carrier_id)→ 限流组 → 操作限制每秒请求数,每次 HTTP 尝试计量,默认限流、仅后台人工点击/通道阈值/自动续费/手动刷新豁免;支持只统计与拦截两种模式,默认只统计 - Asynq 遇限流不计失败、固定短延迟重试;轮询任务与停复机业务重试遇限流短延迟重排 - 停机、复机不再使用网关内部重试,只发一次请求 - 网关返回结构化错误;新增上游错误分类规则表与测试接口 - 新增按月分区的停复机记录表:系统自动停复机只写记录,不再写统一审计与集成日志;普通轮询查询不再写集成日志 - 新增限流组、错误分类、停复机记录、按账户调用统计与枚举后台接口 - 迁移 000243~000246;同步 OpenSpec 提案(含后续 6 个 change)与技术方案、工程约束
38 lines
4.3 KiB
Markdown
38 lines
4.3 KiB
Markdown
## Context
|
||
|
||
完整设计见 `docs/engineering/轮询按运营商账户限流技术方案.md` 第三部分第 6、6.1、6.2、11.5、12 节,第 9 节停复机出口开关。
|
||
|
||
现状:`StopResumeService.EvaluateAndAct`(`stop_resume_service.go:113`)判断后当场调网关,`stopCardWithRetry` / `resumeCardWithRetry` 3 次、每次 sleep 2 秒;设备维度原因抢 `RedisPollingDeviceOpLockKey`(SetNX 30 秒,抢不到跳过)逐张停;`resumeAfterActivation`(4 处)、`reset_service.go:400`、`order/service.go:2196` 起 goroutine 调 `ResumeCardIfStopped`;退款 `refund/service.go:1258` 调 `ManualStopCard`,失败由 Outbox 与 `RecoverRefundPostProcessing` 反复补发;套餐到期由套餐轮询发现。第 5 个 Change 已提供账户队列、分发器、NSQ、失败告警表;第 1 个 Change 已提供限流、错误分类与停复机记录。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:** 自动停复机一个出口、不风暴、不翻转、不丢;设备下各卡一致;暂停轮询的卡套餐失效照常停。
|
||
|
||
**Non-Goals:** 不改停复机业务规则(第 4 个 Change 已定口径);不改后台人工点击、通道阈值、自动续费的停复机;不做提前停机、停机前续费等老系统规则。
|
||
|
||
## Decisions
|
||
|
||
1. **Redis 结构**:账户 `polling:carrier:{cid}:stop_resume`(ZSET)与 `:sr_running`;卡 `polling:card:{id}:stop_resume_cmd`(Hash:动作、原因、场景、来源、版本、失败计数、首次失败、最近错误、待确认)、`:sr_claim`、`:sr_exec`、`:sr_last_call`;设备 `polling:device_eval:queue`(ZSET)、`polling:device:{id}:eval_ver`、`:eval_scenes`(Set)、`:eval_claim`、`:eval_lock`;分发锁 `polling:device_eval:dispatch_lock`。
|
||
2. **统一判断函数**:从 `EvaluateAndAct`、`resumeSingleCard`、`resumeDeviceCards`、`ResumeCardIfStopped` 抽出"输入卡与资产数据 + 场景 → 目标状态与原因",入口与执行器共用;执行部分抽出供执行器调用一次网关。
|
||
3. **版本号**:覆盖指令 `HINCRBY ver`;领取时随消息带版本;收尾 Lua 比较版本决定移除或保留。
|
||
4. **单卡最小重试间隔按卡合计**:上游"卡状态变更每十分钟只允许调用 1 次"对停机与复机共同计数;领取 Lua 校验 `sr_last_call`,执行器调用前写。
|
||
5. **结果未知在队列内闭环**:不复用现有人工核对策略(`GatewayCardCommandUnknownRecoveryStrategy` 只有人工核对);确认查询属于查状态,计入停复机所在限流组,不受单卡最小间隔约束。
|
||
6. **设备判断**:`ZADD LT` 合并;`SADD eval_scenes`;领取带版本与场景快照;收尾 `SREM` 已处理场景并比较版本。锁内只读与写指令,毫秒级;与旧路径的 `RedisPollingDeviceOpLockKey` 互斥。
|
||
7. **全局开关**:`polling.stop_resume_queue_enabled` 受控系统配置;所有入口通过一个"请求评估"门面按开关决定入队还是旧路径当场执行,避免分散判断。停复机与设备判断的分发不受 `scheduler_mode` 限制:开关开启后对全部账户领取(`scheduler_mode = legacy` 时套餐步骤仍在旧 handler 内,同样改为请求评估)。
|
||
8. **退款完成语义**:请求写入成功即标记 `asset_reset`,补偿扫描只覆盖"请求未写入"的情况,不再因停机失败重放。
|
||
9. **到期即停入口**:`package_activation_handler.go` 到期失效处理处判断无其他有效主套餐后请求评估(场景"套餐到期");接续激活路径不变。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- [停复机比现在晚 1~2 秒] → 已接受;停复机先于卡任务领取并可预留 QPS。
|
||
- [请求评估写 Redis 失败] → 记日志,由该卡下一次套餐步骤兜底;暂停轮询的卡套餐失效停机按已定口径不补偿。
|
||
- [流量重置日集中复机] → 按账户停复机 QPS 与执行上限平滑执行,监控队列长度。
|
||
- [开关切换中旧路径与新路径并存] → 设备锁互斥;开关关闭时队列指令保留,重新开启时执行前重新判断。
|
||
|
||
## Migration Plan
|
||
|
||
1. 迁移插入开关(默认关闭)与告警阈值。
|
||
2. 测试环境部署,开关关闭时行为与现状一致;开启后按 tasks 第 10 节核对;演练开、关、再开。
|
||
3. 生产发布后开启开关,首日监控停复机成功率、队列长度与失败告警。
|
||
4. 回退:关闭开关。
|