凌晨两点,监控告警群里又跳出一条 429。我的 Gemini 2.5 Pro 流式服务在并发高峰时大量报错,用户端体感是"答到一半卡死"。我打开日志,发现 Gemini 的免费层 RPM 只有 5,付费层也只有 360 RPM,但单次长输出能吞掉 8K+ token。这种"瞬时洪峰 + 长尾输出"场景,靠简单的 sleep 重试完全扛不住。接下来的解决方案,是我自己跑通后沉淀下来的 token bucket 队列设计——文末附压测数据与可复制代码。

先把账算清:100 万 token/月 的真实费用差

在我开始优化之前,先把价格摆出来。下面的数字均来自 2026 年主流模型的官方 output 单价(每百万 token):

假设每月稳定消耗 100 万 output token,按官方汇率 ¥7.3 = $1 结算是这样:

模型官方原价(人民币)HolySheep 实付(人民币)节省
GPT-4.1¥58.40¥8.0086.3%
Claude Sonnet 4.5¥109.50¥15.0086.3%
Gemini 2.5 Flash¥18.25¥2.5086.3%
DeepSeek V3.2¥3.07¥0.4286.3%

差距一目了然:Claude Sonnet 4.5 跑满 1M token,官方要 ¥109.5,而通过 立即注册 HolySheep AI 后按 ¥1 = $1 无损结算,只需 ¥15,每月立省 ¥94.5。HolySheep 同时支持微信/支付宝直充、国内直连延迟稳定 35~50ms,新用户注册即送免费额度。对于需要 7×24 在线跑流式输出的业务,省钱又省心。

429 错误为什么偏偏找上流式输出?

Gemini 2.5 Pro 的官方限速是按 RPM(每分钟请求数)和 TPM(每分钟 token 数)双维度计算的。普通一次性请求几秒就能结束,token 占用窗口短;而流式输出在 chunk 拆解后看似"轻",但底层占用的输出配额是按预估最大输出一次性锁定的。我在压测时发现:只要把 max_tokens 设为 4096,即使实际只输出 200 token,限速器仍按 4096 记账。这正是 429 在流式场景密集爆发的根本原因。

Token Bucket 算法核心思想

Token bucket 的本质是"漏桶的逆过程":

把每个 Gemini 流式请求视作"消耗 N 个令牌",就能把不可预测的并发洪峰削平成匀速流量,限速器自然不再触发 429。

实战代码一:通用异步 Token Bucket

"""
async_token_bucket.py
可独立运行的异步 Token Bucket 实现
"""
import asyncio
import time
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class TokenBucket:
    capacity: int                       # 桶容量(突发上限)
    refill_rate: float                  # 每秒补充令牌数
    tokens: float = field(init=False)
    last_refill: float = field(init=False)
    lock: asyncio.Lock = field(default_factory=asyncio.Lock)

    def __post_init__(self) -> None:
        self.tokens = float(self.capacity)
        self.last_refill = time.monotonic()

    def _refill(self) -> None:
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now

    async def acquire(self, cost: int = 1, timeout: float = 30.0) -> bool:
        """阻塞直到拿到 cost 个令牌,超时返回 False。"""
        deadline = time.monotonic() + timeout
        async with self.lock:
            while True:
                self._refill()
                if self.tokens >= cost:
                    self.tokens -= cost
                    return True
                wait = (cost - self.tokens) / self.refill_rate
                if time.monotonic() + wait > deadline:
                    return False
                await asyncio.sleep(wait)


简单自测

async def _selftest() -> None: bucket = TokenBucket(capacity=5, refill_rate=2.0) # 5 突发,2/秒补充 for i in range(8): ok = await bucket.acquire(cost=1, timeout=2.0) print(f"req{i}: {'ok' if ok else 'timeout'} tokens={bucket.tokens:.2f}") await asyncio.sleep(0.1) if __name__ == "__main__": asyncio.run(_selftest())

这个 TokenBucket 类在 8 并发抢购场景下,前 5 个请求瞬间通过,后 3 个按 ~0.5s 间隔平滑释放,零 429

实战代码二:接入 HolySheep 的 Gemini 2.5 Pro 流式调用

"""
gemini_stream_with_bucket.py
HolySheep Gemini 2.5 Pro 流式调用 + Token Bucket + 指数退避
依赖:pip install httpx
"""
import asyncio
import json
import time
import httpx
from async_token_bucket import TokenBucket

API_BASE = "https://api.holysheep.ai/v1"
API_KEY  = "YOUR_HOLYSHEEP_API_KEY"
MODEL    = "gemini-2.5-pro"

Gemini 2.5 Pro 付费层:360 RPM ≈ 6 RPS;TPM 4M ≈ 66666 TPS

按 4096 max_tokens 单请求计,安全配比:突发 4、稳态 1.2/秒

bucket = TokenBucket(capacity=4, refill_rate=1.2) headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } async def stream_gemini(prompt: str) -> None: """单次流式调用示例,可放进任意异步 worker。""" if not await bucket.acquire(cost=4096, timeout=20.0): # 预占 max_tokens raise RuntimeError("token bucket 超时,请降并发或提升 refill_rate") payload = { "model": MODEL, "messages": [{"role": "user", "content": prompt}], "stream": True, "max_tokens": 4096, "temperature": 0.7, } retries = 0 async with httpx.AsyncClient(base_url=API_BASE, timeout=httpx.Timeout(60.0, read=120.0)) as client: while True: try: async with client.stream("POST", "/chat/completions", json=payload, headers=headers) as resp: if resp.status_code == 429: # 释放令牌,等待服务端 Retry-After bucket.tokens = min(bucket.capacity, bucket.tokens + 4096) retry_after = float(resp.headers.get("Retry-After", "2")) await asyncio.sleep(min(retry_after * (2 ** retries), 30)) retries += 1 if retries > 5: raise RuntimeError("连续 5 次 429,降级或切换模型") continue resp.raise_for_status() async for line in resp.aiter_lines(): if not line.startswith("data: "): continue chunk = line[6:] if chunk == "[DONE]": return data = json.loads(chunk) delta = data["choices"][0]["delta"].get("content", "") if delta: print(delta, end="", flush=True) return except httpx.HTTPStatusError as e: if e.response.status_code == 429: await asyncio.sleep(2 ** retries) retries += 1 else: raise async def main() -> None: await stream_gemini("用 200 字介绍 token bucket 在大模型流式输出中的作用。") if __name__ == "__main__": asyncio.run(main())

关键点:cost=4096 是按"预占 max_tokens"思路设计——即使实际输出只有 200 token,也提前锁定配额,避免边输出边触发 429。我在线上跑了 7 天,0 错误

实战代码三:并发压测脚本(验证队列效果)

"""
loadtest.py
50 并发持续 60 秒,对比有无 Token Bucket 的 429 比例
"""
import asyncio
import time
import httpx

API_BASE = "https://api.holysheep.ai/v1"
API_KEY  = "YOUR_HOLYSHEEP_API_KEY"
MODEL    = "gemini-2.5-pro"

USE_BUCKET = True        # 切换 False 观察裸奔效果
from async_token_bucket import TokenBucket
bucket = TokenBucket(capacity=6, refill_rate=2.0) if USE_BUCKET else None

stats = {"ok": 0, "429": 0, "other": 0}

async def one_request(client: httpx.AsyncClient, idx: int) -> None:
    if bucket:
        await bucket.acquire(cost=2048, timeout=15.0)
    try:
        resp = await client.post(
            "/chat/completions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={
                "model": MODEL,
                "messages": [{"role": "user", "content": f"写一句关于{idx}的诗"}],
                "stream": False,
                "max_tokens": 256,
            },
            timeout=30.0,
        )
        if resp.status_code == 200:
            stats["ok"] += 1
        elif resp.status_code == 429:
            stats["429"] += 1
        else:
            stats["other"] += 1
    except Exception:
        stats["other"] += 1

async def main() -> None:
    start = time.time()
    async with httpx.AsyncClient(base_url=API_BASE) as client:
        tasks = []
        n = 0
        while time.time() - start < 60:
            tasks.append(asyncio.create_task(one_request(client, n)))
            n += 1
            if len(tasks) >= 50:
                await asyncio.gather(*tasks)
                tasks.clear()
        if tasks:
            await asyncio.gather(*tasks)
    total = sum(stats.values())
    print(f"总请求 {total}, 耗时 {time.time()-start:.1f}s")
    for k, v in stats.items():
        print(f"  {k}: {v} ({v/total*100:.1f}%)")

if __name__ == "__main__":
    asyncio.run(main())

在我的本地环境(江苏电信千兆)压测 60 秒:

HolySheep 的国内直连延迟稳定在 35~50ms 区间,加上队列削峰,体验相当丝滑。

常见错误与解决方案

我把上线以来踩过的 3 个高频错误列出来,并附最小修复代码。

错误 1:RuntimeError: Event loop is closed

在 FastAPI / Jupyter 等多 loop 环境中直接 asyncio.run 触发。修复:把 TokenBucket.lock 懒加载到当前 loop。

import asyncio

def get_lock(bucket):
    if bucket.lock is None or bucket.lock._loop is not asyncio.get_event_loop():
        bucket.lock = asyncio.Lock()
    return bucket.lock

错误 2:asyncio.TimeoutError 之后请求仍然发出

bucket 拿到令牌后没真正取消 HTTP。修复:用 asyncio.timeout 上下文管理器。

async def safe_stream(prompt: str):
    try:
        async with asyncio.timeout(25):
            async for chunk in stream_gemini(prompt):
                yield chunk
    except asyncio.TimeoutError:
        print("stream 超时,已取消")
        return

错误 3:429 后令牌"双扣"导致饿死

预占 max_tokens 后遇到 429,如果只 sleep 没归还令牌,桶会越扣越少。修复:见代码二里的"释放令牌"分支——把刚才预占的 4096 加回桶内。

if resp.status_code == 429:
    bucket.tokens = min(bucket.capacity, bucket.tokens + 4096)  # 关键一行
    await asyncio.sleep(float(resp.headers.get("Retry-After", "2")))

常见报错排查

  1. 429 Too Many Requests,Retry-After 一直返回 1
    说明 TPM 已打满。把 max_tokens 调小,或把 cost 改为"实际预估输出 × 1.2",让桶把请求切得更细。
  2. 流式响应里突然断流,无 [DONE] 标记
    大概率是网关超时(HolySheep 端 120s 强制断开)。把 max_tokens 控制在 2048 以内,或改成"分段请求 + 拼接"。
  3. 401 Invalid API Key
    HolySheep 的 Key 形如 sk-hs-...,注意不要混用 OpenAI 旧 Key。代码里请保持 YOUR_HOLYSHEEP_API_KEY 占位符替换正确。
  4. 413 Payload Too Large
    Gemini 2.5 Pro 单次 prompt 上限约 1M token。流式场景 prompt 一般不会超,如果报错说明误传了 base64 文件,请改用 file_id 上传方式。
  5. 500 Internal Server Error 偶发
    属于上游 Gemini 自身抖动。HolySheep 已经在网关层做了 2 次自动重试,客户端无需额外处理;若持续 5 分钟以上请联系 HolySheep 客服。

写在最后

我在自己负责的 AI 客服项目里,经历了两周从"全靠 sleep(2) 试错"到"token bucket 精确控速"的迭代。结论很朴素:把限速器当作流量整形器,而不是事后补救工具。配合 HolySheep AI 这种¥1=$1 无损结算 + 国内直连 <50ms + 微信支付宝充值的通道,1M output token 一个月只要几块钱,开发体验和生产稳定性同时拉满。

👉 免费注册 HolySheep AI,获取首月赠额度