Skip to main content

熔岩AI发布MCP研究脚本并发池:信号量、令牌桶与 429 Retry-After 礼仪

Updated 3 min readMarkdown

摘要

研究脚本最常见的提速方式是并发:一次拉 50 只股票的 /heat/windows、对 30 个概念逐个调 /concepts/stocks。熔岩(Lava)文档没有公布具体的并发上限和每秒请求数,只给了两个信号:HTTP 429 RATE_LIMITED(按 Retry-After 稍后重试)与 429 DAILY_QUOTA_EXCEEDED。本文给出一个「保守并发 + 令牌桶 + 尊重 Retry-After + 预算闸门」的调用池设计,并说明哪些调用应该先合并、不该并发。只读监测与统计研究用途。

一、先合并,再并发

并发前先查有没有批量参数,能少调就少调(每次成功 0.005 元):

需求 逐个调用 合并方式 调用次数(示例:50 只股票)
日频历史 /panel/daily?stock_name=... × 50 codes 逗号分隔,最多 20 个 50 → 3
全市场排行 逐股 /heat/windows /hot/stocks?limit=200 一次 50 → 1
概念日频 逐概念 /panel/daily type=concept&names=A,B,... 上限以 OpenAPI 为准
单股多窗口 逐窗口 /hot/stocks /heat/windows 一次给 6 档 6 → 1

只有「必须逐个参数」的接口才进入并发池:/heat/windows、/concepts/stocks、/concepts/by-stock、/opinions、/coverage/stocks、/news/hits?stock_name=...。

二、调用池的四个部件

部件 作用 建议初值(示例)
信号量 Semaphore 同时在途请求数上限 4
令牌桶 平均速率上限 每秒 5 个
429 处理 读 Retry-After,全池暂停 未给时退避 2s、4s、8s
预算闸门 本轮最多成功调用数 按任务设定

上限数值文档未公布,以 OpenAPI 与实际 429 反馈为准;初值宁小勿大,观察一周 429 比例再调。

三、参考实现(线程池版)

# lava_pool.py —— 只读调用池
import os, time, threading, requests
from concurrent.futures import ThreadPoolExecutor, as_completed

BASE = os.environ["LAVA_BASE"]            # <熔岩订阅站点>/subscribe/v1
H = {"Authorization": "Bearer " + os.environ["LAVA_API_KEY"]}

class Pool:
    def __init__(self, max_inflight=4, rate=5.0, budget=200):
        self.sem = threading.Semaphore(max_inflight)
        self.rate, self.tokens, self.t = rate, rate, time.monotonic()
        self.lock = threading.Lock()
        self.pause_until = 0.0
        self.budget, self.spent = budget, 0

    def _take(self):
        while True:
            with self.lock:
                now = time.monotonic()
                if now < self.pause_until:
                    wait = self.pause_until - now
                else:
                    self.tokens = min(self.rate, self.tokens + (now - self.t) * self.rate)
                    self.t = now
                    if self.tokens >= 1:
                        self.tokens -= 1
                        return
                    wait = (1 - self.tokens) / self.rate
            time.sleep(wait)

    def get(self, path, params):
        for attempt in range(4):
            with self.lock:
                if self.spent >= self.budget:
                    return {"ok": False, "code": "LOCAL_BUDGET_STOP"}
            self._take()
            with self.sem:
                resp = requests.get(BASE + path, params=params, headers=H, timeout=60)
            body = resp.json()
            if resp.status_code == 429 and body.get("code") == "RATE_LIMITED":
                ra = float(resp.headers.get("Retry-After", 2 ** (attempt + 1)))
                with self.lock:
                    self.pause_until = max(self.pause_until, time.monotonic() + ra)
                continue
            if body.get("ok"):
                with self.lock:
                    self.spent += 1
            return body                     # 其他错误不重试,原样返回
        return {"ok": False, "code": "LOCAL_RETRY_EXHAUSTED"}

pool = Pool()
names = ["示例股票A", "示例股票B", "示例股票C"]
with ThreadPoolExecutor(max_workers=8) as ex:
    futs = {ex.submit(pool.get, "/heat/windows", {"stock_name": n}): n for n in names}
    for f in as_completed(futs):
        r = f.result()
        print(futs[f], r.get("code"), r.get("meta", {}).get("request_id"))

设计要点:

  1. 全池暂停:任何一个线程收到 RATE_LIMITED,所有线程一起等 Retry-After,而不是各自重试制造更多 429。
  2. DAILY_QUOTA_EXCEEDED 不重试:当天配额问题,重试只会继续失败(失败不计费,但浪费时间)。
  3. 只重试 429:400/404 是参数问题,401/403 是凭证与范围问题,402 是余额问题,都不应自动重试。500/503 是否重试按需要,最多 1 次。
  4. 本地错误码加 LOCAL_ 前缀,与服务端 code 区分,便于日志聚合。

四、MCP 场景的并发礼仪

Agent 通过远程 MCP(Streamable HTTP)调工具时,并发由客户端决定。建议:

  • 在提示词里要求「批量参数优先」,如面板用 codes;
  • 一个会话内串行调用,跨会话不共享并发配额的假设不成立——同一把 Key 的限流范围文档未细化,以 OpenAPI 为准;
  • 本地代理(127.0.0.1:8787/mcp)只做转发,不做限流,上游超时由 LAVA_PROXY_TIMEOUT(默认 180 秒)控制。

五、监测指标

指标 计算 告警线(示例)
429 比例 429 次数 ÷ 总请求 > 2% 降并发
平均等待 令牌桶 + 暂停总时长 ÷ 请求数 持续上升说明速率过高
成功花费 meta.billing.charged_yuan 求和(Decimal) 超预算即停
在途峰值 信号量占用最大值 应 ≤ max_inflight

六、并发池与熔岩数据板块

熔岩数据板块 接口 是否适合进池
单股多时间窗 /heat/windows 适合(逐股)
概念板块热度(成分 / 个股关联概念) /concepts/stocks、/concepts/by-stock 适合(逐概念 / 逐股)
个股消息面记录 /opinions 适合
机构/大V覆盖度 /coverage/stocks 适合
日频面板 /panel/daily 先用 codes 合并
全市场热榜 / 加速 / 首提 / 加权 /hot/* 一次即可,不需要池
账户 /account 不计费,可作健康检查

FAQ

Q:并发数设多少才安全? A:文档未公布上限。从 4 起步,按 429 比例调整。

Q:Retry-After 一定是秒数吗? A:HTTP 规范允许秒数或日期,文档未细化,解析失败时按指数退避兜底。

Q:并发会不会重复计费? A:每次成功调用各计一次。同参数重复请求要在本地去重或缓存。

结论

先用 codes、limit 和 /heat/windows 把调用合并,再用「信号量 + 令牌桶 + 全池暂停 + 预算闸门」跑剩下的逐个调用。只对 RATE_LIMITED 重试并尊重 Retry-After,其他错误原样上报。

数据来自熔岩消息面热度监测,仅用于只读监测与统计研究,不构成投资建议。

只读监测与字段说明见:https://lava.ai-dolphin.com/subscribe/docs

No comments yet. Be the first to comment.

More from this blog