news 2026/10/9 7:46:33

AI调用的限流机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI调用的限流机制

引言

在 AI 应用开发中,调用大模型 API 时经常会遇到429 Too Many Requests错误。这是因为模型厂商对每个账号的请求频率(RPM)和 Token 消耗(TPM)都有限制。当你的应用用户量增长、并发请求增多时,如何优雅地控制请求速率、避免触发厂商限流、同时保证用户体验,就成了一个必须解决的问题。

本文将从「为什么要限流」讲起,逐步深入令牌桶算法的原理与实现,再探讨多实例部署下的分布式限流方案,最后介绍如何用 Resilience4j 框架优雅地落地限流与重试,并顺带讨论重试带来的幂等性问题。读完本文,你将能独立设计并实现一套完整的 AI 应用限流方案。

建立一个模型概念

你的用户 A: "帮我翻译" ─┐ 你的用户 B: "帮我写代码" ─┤ 你的用户 C: "帮我分析数据" ─┼──→ 你的服务器 ──→ OpenAI(RPM=60) 你的用户 D: "帮我写作文" ─┤ 你的用户 E: "帮我改简历" ─┘ 如果不限流 → 1000 个用户同时请求 → 瞬间打满 60 次额度 → 后面全部 429 → 用户看到:"请求失败,请稍后重试" → 用户体验极差

为什么要限流?

  • 模型厂商给我们的RPM有限,超过了就限流
  • 我们自己写的一个项目如果是很多人共用了一个api的话,1000个用户同时请求,RPM就瞬间被打满,用户体验差

限流的机制:令牌桶### 主流的简单的机制是漏桶

  • 就是MQ的意思,不管上游有多么急,流出永远匀速
  • 实现很简单,但是空闲时间的额度就浪费了

但是一般用的是令牌桶

  • 假设你的 API 限流规则是 RPM = 10(每分钟最多 10 次请求)
  • 填充速率 refillRate = 10次/分钟
  • 填充速率是往桶里面填充的速率
  • 桶容量 capacity = 3
  • 请求来了就拿一个令牌,拿了令牌才能成功调用
  • 有桶存这了就意味着可以短时间处理3个请求
  • 但是要设置上限流,防止一个人空闲很久后一次性把所有"额度"全用掉,把系统打爆
  • 根据上面这个话我推测是每一个用户一个令牌桶

没有令牌怎么办

  • 两种取法:tryAcquire(没令牌就立刻拒绝 → 快速降级)和acquire(没令牌就排队等 → 削峰)

令牌的实现

第一反应

//开一个后台线程,每隔 200ms 往桶里 +1 个令牌while(true){sleep(200ms);bucket.tokens+=1;// 定时器加令牌}
  • 首先开线程有开销
  • 其次sleep睡的不精确、
  • 同时取和放对于线程安全又是一个难题

惰性计算:不主动加,用的时候再算

  • 不真的"发放"令牌,来取的时候,再算"从上次到现在,该长出多少个
  • 就像下面这样
  • 第 16ms:请求 A 来了
计算:距上次过了多久? elapsed = 16ms - 0ms = 16ms 该长多少令牌? grown = 16ms × 0.005 = 0.08 个 桶里现在多少? tokens = min(3.0, 3.0 + 0.08) = 3.0 ← 满了,多了也不要 够不够?3.0 >= 1 → 够! 扣减:tokens = 3.0 - 1 = 2.0 更新:lastRefillMs = 16ms ✅ 放行

为什么需要同时检查两个桶?

你可能会有疑问:既然每个用户有自己的个人桶,为什么还要检查模型的全局桶?

原因很简单:两个桶保护的是不同的资源。

  • 个人桶保护的是「单个用户」——防止某个用户独占所有配额,导致其他用户无法使用
  • 全局桶保护的是「模型厂商的额度」——即使每个用户都只用了少量额度,1000 个用户加起来也可能瞬间打满厂商的 RPM

举个例子:假设 GPT-4 的全局 RPM=60,你有 100 个用户,每个用户个人桶 RPM=20。如果只检查个人桶,100 个用户同时请求时,每个用户都只用了 1 次(远低于 20),但总请求量已经达到 100 次,远超全局 60 的额度,厂商照样会返回 429。

所以,两个桶缺一不可——个人桶管公平,全局桶管总量。

多令牌桶并存

你的系统服务多个模型、多个用户: 桶 1:GPT-4 专用桶 → RPM=60, TPM=100000 桶 2:GPT-3.5 专用桶 → RPM=500, TPM=500000 桶 3:用户 A 个人桶 → 每分钟最多 20 次(防他一个人吃满配额) 桶 4:用户 B 个人桶 → 每分钟最多 20 次 每次请求要同时检查两个桶: "这个用户的个人桶" 有没有额度? "这个模型的全局桶" 有没有额度? 两个都够 → 放行 任一不够 → 拒绝
  • 要看全局和个人是不是都有额度

为什么不能用「每台机器均分额度」?

有人可能会想:既然有 3 台机器,那把全局额度 3 均分给每台机器(每台 1 个)不就行了吗?

这个方案有两个致命问题:

  1. 负载不均衡:请求通过负载均衡随机分配,某台机器可能瞬间收到大量请求,而其他机器却很空闲。均分额度会导致「闲的闲死、忙的忙死」——空闲机器的额度浪费了,繁忙机器的额度不够用
  2. 扩容/缩容困难:机器数量变化时,需要重新计算每台的额度,运维成本高

所以,正确的做法是把令牌桶放到所有实例共享的公共存储中——也就是 Redis。所有实例从同一个桶里取令牌,天然保证全局额度一致。

多实例下的坑

  • 多实例是指服务器部署到不同机器
你的服务部署了 3 台机器(为了高可用和负载均衡) 机器1:桶容量 3 ←── 各自独立,互不知道对方的存在 机器2:桶容量 3 机器3:桶容量 3 用户请求通过负载均衡随机分配: → 10 个请求进来 → 机器1 分到 4 个,放行 3 个 → 机器2 分到 3 个,放行 3 个 → 机器3 分到 3 个,放行 3 个 实际放行了 9 个!但你配的全局额度应该只有 3 个突发! 总出站 = 3 × 3 = 9(应该是 3) 厂商:你超了 → 429 ❌
  • 很显然,要存到公共的地方——Redis里面

框架写限流

  • 框架是Resilience4j,已经集成到langchain4j里面去了
  • 然后限流也是一个装饰器
/** * W7 限流装饰器:和 W6 的缓存/路由/计量装饰器同层,包在出站模型调用外。 * 每次调用前先向 Resilience4j 令牌桶取许可,取不到就按 timeoutDuration 排队等待, * 等不到会抛 RequestNotPermitted(上层可接住后降级)。 */publicclassRateLimitedChatModelimplementsChatModel{privatefinalChatModeldelegate;privatefinalRateLimiterrateLimiter;publicRateLimitedChatModel(ChatModeldelegate,RateLimiterrateLimiter){this.delegate=delegate;this.rateLimiter=rateLimiter;}@OverridepublicChatResponsedoChat(ChatRequestrequest){// 取许可 + 调用:Resilience4j 帮你完成 acquire/等待/拒绝,替代手写令牌桶returnrateLimiter.executeSupplier(()->delegate.chat(request));}@OverridepublicChatRequestParametersdefaultRequestParameters(){returndelegate.defaultRequestParameters();}}

框架写重试

  • 代码就不贴上去了
  • 然后重试也是一个装饰器
计量Decorator └── 缓存Decorator └── 重试Decorator ← 重试在这里,也是装饰器 └── 限流Decorator └── 路由Decorator └── 模型(真正的 LLM 调用)
  • 计量最外面?可能还要记缓存命中率啥的
  • 然后重试应该放在限流外面
  • 因为重试也需要拿令牌才行,不然就绕过了限流

不停的调用就会涉及到一个幂等的问题吧

  • 为了多次调用工具保持幂等性,就引入了幂等键
你的Agent应用 │ ├──LLM层(langchain4j 管) │ └── 决定调哪个工具 ← 模型只负责"决策"│ ├── 工具实现(你自己写) ← 幂等在这里! │ └──@Tool│createOrder(idempotencyKey){│if(redis.setnx(key)){// 没见过,执行│doCreate();│}else{// 见过了,返回旧结果│returnredis.get(key);│}│}│ └── 外部服务(工单系统等)
  • 在调用工具层加一个幂等键的方法
请求带着 key="abc-123" 进来 │ ▼ Redis 里有这个 key 吗? │ ┌───┴───┐ 没有 有 │ │ ▼ ▼ 执行操作 直接返回之前存的结果 存结果 {key → 结果}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 7:46:05

【ArkUI进阶练中学】第10课:架构设计与工程化最佳实践

本节目标 掌握大型 HarmonyOS 应用的模块拆分策略,理解按业务域、按功能层、按团队边界三种拆分维度的适用场景掌握依赖治理的核心原则,能够使用 ohpm 的 override、resolve_conflict 和依赖分析工具控制依赖数量与版本一致性掌握构建优化的关键配置&…

作者头像 李华
网站建设 2026/10/9 7:44:47

Android开发环境搭建全攻略:JDK/SDK/Gradle配置与实战

1. 开篇:这个系列要做什么,环境为什么值得单独讲一课做啥子嘛,这四个字是四川话里特别日常的一句,意思就是"做什么呢""干嘛呢"。拿它当项目名,是因为这个系列要做的APP本身就是一个帮你解决"…

作者头像 李华
网站建设 2026/10/9 7:43:21

【Jetpack Compose基础语法学与练】第2课 State状态 + 点击交互

前言 上一节课我们完成最简HelloWorld,认识了 Composable 可组合函数、预览注解。 Compose声明式UI的核心:界面跟随状态自动更新。 本课重点学习状态定义、记忆状态、按钮点击事件,打通「状态‑重组‑界面刷新」完整闭环。 前置:完…

作者头像 李华
网站建设 2026/10/9 7:43:00

Maven从下载到配置全流程:环境变量、镜像加速与避坑指南

用过Java的人都绕不开Maven,但能把Maven顺顺利利装到本地的人,我还真没见过几个。多数人卡住的第一个环节就是“下载”,别笑,这个看似简单的步骤里全是坑:官网慢得像蜗牛、下载到一半断掉、好不容易下完了发现跟JDK版本…

作者头像 李华