缺陷分析:API 限流排队拥塞恒定 30 个请求 2
BUG 分析:网络恢复后 API 限流排队深度始终卡在 30
摘要
- 现象:WebSocket 重连成功后,日志中「API 限流排队拥塞」的「排队深度」长期停留在约 29~30 个请求,「需等待」约 74s,看似队列不消化。
- 影响:重连后 K 线 HTTP 拉取缓慢、日志持续刷拥塞告警,易被误判为限流或配置错误。
- 根因:「排队深度」表示的是当前请求前已预约的时间槽个数,不是队列长度上限;分析 worker 数为 30,全局限流每 2.5s 一个槽位,稳态下 30 个 worker 持续占满槽位,故排队深度恒为约 30。
完整因果链
1. 输入(Input)
| 类型 | 描述 |
|---|---|
| 主路径 | WebSocket 假活检测触发重连 → 重连成功 → 「清空缓存,强制 HTTP 刷新」→ 大量分析任务需要 K 线数据。 |
| 触发表现 | 30 个分析 worker 同时调用 fetch_candles(经 KlineDataFiller → fetch_candles_range_with_retry),全局限流器按 2.5s 一槽串行化,每个新请求看到的「前面已预约槽位数」≈ 30。 |
即:输入 = 重连后大量 candle 请求 + 30 个并发 worker 竞争同一全局限流。
2. 状态与公式(State)
- 限流机制:单全局变量
_next_allowed_time,每次fetch_candles在锁内预约「下一个可用时间」wait_until = max(now, _next_allowed_time),并置_next_allowed_time = wait_until + 2.5,再在锁外 sleep 到wait_until后发请求。 - 排队深度定义:
queue_depth = (wait_until - now) / min_interval,其中min_interval = KLINE_FILLER_API_INTERVAL = 2.5。即当前请求前面已预约的槽位数,非「队列中等待的请求数」。 - 稳态:30 个 worker 持续占满槽位 →
wait_until - now恒约 30×2.5 ≈ 75s → 排队深度 ≈ 30,需等待 ≈ 74s。每完成 1 个请求又有新任务立即预约,故数值不下降直至整体任务量减少。
状态变化链:
[ 重连成功,缓存失效/强制刷新 ]
→ [ 大量分析任务入队,30 个 worker 同时拉 K 线 ]
→ [ 每次 fetch_candles 预约 2.5s 槽位,_next_allowed_time 持续前移 ]
→ [ 新请求看到的 wait_until - now ≈ 75s,queue_depth ≈ 30 ]
→ [ 日志持续输出「排队深度: 30」「需等待: 74s」]
3. 调用路径(Call Path)
WebSocket 重连成功
└─ 清空缓存 / 缓存过期(如 base_klines TTL 60s)
└─ 分析队列中大量任务(symbol × 周期)需 K 线
└─ 30 × _analysis_worker 并发处理
└─ _fetch_and_validate_price_data() → KlineDataFiller.fill_*()
└─ fetch_candles_range_with_retry() → fetch_candles()
└─ with _candles_lock:
wait_until = max(now, _next_allowed_time)
_next_allowed_time = wait_until + 2.5
queue_depth = (wait_until - now) / 2.5
└─ sleep(wait_until - now) # 约 74s
└─ info.candles_snapshot(...)
4. 为何是 30 而不是「队列上限」
- 代码中没有将排队深度或队列长度上限设为 30 的硬编码。
- 30 = ANALYSIS_WORKERS_GENERAL(分析工作线程数)。在「每 2.5s 一槽」的全局限流下,约 30 个并发调用者会使得「下一个可用槽位」始终在约 75s 之后,因此计算得到的排队深度稳定在约 30。
- 因此这是并发数与限流间隔共同导致的稳态现象,不是配置了「最多排队 30 个请求」的队列上限。
涉及文件与配置
| 项目 | 位置 |
|---|---|
| 排队深度计算与「API 限流排队拥塞」日志 | src/utils/hyperliquid_candles.py 第 99–115 行 |
| 全局限流间隔 | src/config.py:KLINE_FILLER_API_INTERVAL = 2.5 |
| 分析 worker 数 | src/config.py:ANALYSIS_WORKERS_GENERAL = 30 |
| 分析 worker 启动 | src/services/realtime_kline_service_base.py:_init_worker_threads() |
| 重连「清空缓存」日志 | src/trading/executor.py 重连回调 |
可选改进方向
-
重连恢复期降低 candle 请求并发
在检测到「刚重连」的一段时间内,减少参与fetch_candles的并发数(如临时减少 worker 或集中到串行/小批量队列),使「前面已预约槽位」少于 30,排队深度与需等待时间下降。 -
重连后 K 线优先 DB,再按需补数
重连后优先从 DB 读已有 K 线,仅对缺失或过期窗口用 filler 做 HTTP 补充,减少同一时刻的 HTTP 请求数。 -
调整 KLINE_FILLER_API_INTERVAL
若需进一步降低 429 或减轻服务器压力,可适当增大间隔(如 3s/4s);在 worker 数不变下,排队深度计算方式不变,整体吞吐略降。 -
文档与观测
在运维/开发文档中说明:重连后「排队深度: 30」是 30 个 worker 与 2.5s 间隔下的稳态现象,会缓慢消化,非 bug;可通过观察「需等待」是否随时间缓慢前移确认队列在消化。
参考
- 全局限流设计见
src/utils/hyperliquid_candles.py模块注释与fetch_candles实现。 - 相关计划说明:
.cursor/plans/下 API 限流排队卡在 30 分析计划。