Convex 限速器组件
这一构成部分规定了应用水平税率限制.
茶师:
const rateLimiter = new RateLimiter(components.rateLimiter, {
freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },
sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },
});
// Restrict how fast free users can sign up to deter bots
const status = await rateLimiter.limit(ctx, "freeTrialSignUp");
// Limit how fast a user can send messages
const status = await rateLimiter.limit(ctx, "sendMessage", { key: userId });
// Use the React hook to check the rate limit
const { status, check } = useRateLimit(api.example.getRateLimit, { count });
使用详情见下文.
利率限制是什么?
限制利率是控制行动频率的技术 通常在服务器上 有多种实现 大部分在网络层运行.
如何限制应用层比率?
应用层限制率发生在您的应用代码中 处理认证、授权和其他业务逻辑。 它允许你 更公平地执行政策。 这不是第一次 尖端DDOS攻击的防线(幸好这是极好的) ),但将服务于大多数现实世界使用的案例.
这种办法有什么区别?
- 类型安全使用:您不会意外错失一个速率限制名称.
- 用于固定窗口或符号桶算法的配置 .
- 高效存储和计算:存储与请求不相称.
- 配置限制,适用于背景中可扩展性的更新.
- 交易评价:如果您 变异失败 .
- 通过信用"保全"提供公平性担保:使自己免于指数化 备份.
- 通过可配置的“滚动”或“爆发”津贴
capacity。 。 。 . - 故障关闭, 不打开 : 当交通超过时, 避免连锁故障 利率限制.
见相关的 堆叠站 为了更多 细节和背景.
预先要求: Convex
你需要一个现有的 Convex 项目来使用组件。 Convex 是一个主机 后端平台,包括数据库、无服务器功能和多一个吨 可以学习 这里。 。 。 .
运行 npm create convex 或遵循其中任何一条
快速启动 来设置一个.
安装
安装组件包 :
npm install @convex-dev/rate-limiter
创建一个 convex.config.ts 应用程序中的文件 convex/ 文件夹并安装
通过调用 use数字 :
// convex/convex.config.ts
import { defineApp } from "convex/server";
import rateLimiter from "@convex-dev/rate-limiter/convex.config.js";
const app = defineApp();
app.use(rateLimiter);
export default app;
定义利率限制 :
import { RateLimiter, MINUTE, HOUR } from "@convex-dev/rate-limiter";
import { components } from "./_generated/api";
const rateLimiter = new RateLimiter(components.rateLimiter, {
// One global / singleton rate limit, using a "fixed window" algorithm.
freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },
// A per-user limit, allowing one every ~6 seconds.
// Allows up to 3 in quick succession if they haven't sent many recently.
sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },
failedLogins: { kind: "token bucket", rate: 10, period: HOUR },
// Apply updates asynchronously for eventual consistency.
llmTokenBudget: {
kind: "token bucket",
rate: 10,
period: HOUR,
applyUpdates: "asynchronously",
},
// Use sharding to increase throughput without compromising on correctness.
llmTokens: { kind: "token bucket", rate: 40000, period: MINUTE, shards: 10 },
llmRequests: { kind: "fixed window", rate: 1000, period: MINUTE, shards: 10 },
});
- 如果您想要定义不同的 如果密钥不重合的话 费率会分开.
- 单位:
period数以毫秒计.MINUTE以上为60000。 。 。 .
战略:
这个 token bucket 方法为总体消费提供了保障
页:1 rate 百分比 period 添加符号,同时允许未使用
累积( 如“ 翻转 ” 分钟) 到一些 capacity 数值。 这么说吧
通常每分钟发送10个,容量20个,然后每2个
几分钟你就可以发送20, 或者在最后两分钟你只发送5,你
现在可以发送15.
这个 fixed window 不同的方法是: 指示牌在
一次,两次 period 毫秒长. 类似地可以累积"翻转"
符号最多 a capacity (对 rate 两个费率限额
(战略) 您可以指定自定义 start 时间,例如如果您想要时段
以在一天的特定时间重设。 默认是随机帮助空间
退出正在重试的请求.
使用量
使用简单的全局率限制 :
const { ok, retryAfter } = await rateLimiter.limit(ctx, "freeTrialSignUp");
ok是否成功消耗了资源retryAfter当它将来成功的时候.
说明: 如果您有许多客户使用 retryAfter 决定何时
重试,防御
雷鸣群起 以
添加一些内容 抖动或使用 reserve 函数
讨论 下级。 。 。 .
用户比率限制 :
使用 key 以使用特定于一些用户 / 团队 / 会话 ID 等的速率限制.
const status = await rateLimiter.limit(ctx, "sendMessage", { key: userId });
设定自定义计数
默认情况下,每次呼叫 limit 以一个单位计算。 过 count 来定制.
// Consume multiple in one request to prevent rate limits on an LLM API.
const status = await rateLimiter.limit(ctx, "llmTokens", { count: tokens });
自动掷出
默认它会返回 { ok, retryAfter }自动投出
当超过限制时,使用 throws它会扔出一个 ConvexError 与
RateLimitError 数据(data: {kind, name, retryAfter}(而不是返回)
何时 ok 是虚假的.
// Automatically throw an error if the rate limit is hit
await rateLimiter.limit(ctx, "failedLogins", { key: userId, throws: true });
检查一个速率限制而不消耗
const status = await rateLimiter.check(ctx, "failedLogins", { key: userId });
信贷能力恢复到利率限度
// Give back the token that was consumed for a request that failed.
await rateLimiter.credit(ctx, "sendMessage", { key: userId });
// Credit a custom count, e.g. to settle a reservation made up-front.
await rateLimiter.credit(ctx, "llmTokens", { count: reserved - used });
它的顶端是 capacity (违约) rate) (中文(简体) ). 任何多余的都被丢弃.
对a 软化 费率限制, 两个随机 硬块被选中,信用被分成两部分,这样它们就完了 尽可能接近相同值。 任何剩余信用都被放弃.
重设费率限制
// Reset a rate limit on successful login
await rateLimiter.reset(ctx, "failedLogins", { key: userId });
内置/ 动态定义费率限制
// Use a one-off rate limit config (when not named on initialization)
const config = { kind: "fixed window", rate: 1, period: SECOND };
const status = await rateLimiter.limit(ctx, "oneOffName", { config });
使用反应钩
您可以使用 React 钩子检查浏览器代码中的速率限制 .
首先,定义服务器 API 以获得速率限制值 :
// In convex/example.ts
export const { getRateLimit, getServerTime } = rateLimiter.hookAPI(
"sendMessage",
{
// Optionally provide a key function to get the key for the rate limit
key: async (ctx) => await getUserId(ctx),
// To allow the client to provide the key, pass a function that takes the key from the client
key: async (ctx, keyFromClient) => {
await ensureUserCanUseKey(ctx, keyFromClient);
return keyFromClient;
},
},
);
然后,使用反应钩检查速率限制:
function App() {
const { status: { ok, retryAt }, check } = useRateLimit(api.example.getRateLimit, {
// [recommended] Allows the hook to sync the browser and server clocks
getServerTimeMutation: getServerTime,
// [optional] The number of tokens to wait on
count: 1,
});
// If you want to check at specific times and get the concrete value:
const { value, ts, config, ok, retryAt } = check(Date.now(), count);
直接获取当前值
如果您想知道, 您可以直接获取利率限制的当前值 上次更新的具体值和时间戳.
const { config, value, ts } = await rateLimiter.getValue(ctx, "sendMessage", {
key: userId,
});
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你还可以用 calculateRateLimit 以计算给定值
时间戳 :
import { calculateRateLimit } from "@convex-dev/rate-limiter";
const { config, value, ts } = calculateRateLimit(
{ value, ts },
config,
Date.now(),
count || 0,
);
避免同步更新的争议
当许多请求同时出现时,他们都可以试图修改 数据库中的数值相同。 由于Convex提供强大的交易 永远不覆盖对方, 所以你不必担心利率 限制比它更经常成功。 然而,当有高 有关这些价值的争论,它引起 乐观货币控制冲突。 。 。 . Convex用后退多次自动重试这些,但它是 最好还是避开他们.
使用 自动同步 限速器的模式有助于增加
在避免OCC冲突的同时,限制费率。 在那个 RateLimiter 配置, 设置
极限 applyUpdates 改为 "asynchronously"数字 :
const rateLimiter = new RateLimiter(components.rateLimiter, {
llmTokens: {
kind: "token bucket",
rate: 40000,
period: MINUTE,
applyUpdates: "asynchronously",
},
});
更新不是在交易中更新利率限制,而是放在队列中 适用于背景 批发工人。检查限制 在一个突变中读取从一个陈旧的数据库快照, 所以这些速率限制 呼声永远不会与同时出现的作者相冲突.
取舍是同步极限最终一致. 一个大 在工人申请足够之前,同时召集人的突围都能够成功 消耗以阻断新呼叫者,这会导致限制变为负数. 说明 消费仍然被计算在内。 当同步更新超过时 未来的请求会被拒绝,直到足够 从负结余中恢复过来的时间已经过去.
如果需要执行精确的瞬间上限 用于您的使用案例,请考虑
使用 shards。 。 。 .
用硬块限制比例
在保持交易性的同时增加吞吐量的一种办法是: "硬"的利率限制。 我们把总容量拆成一桶, 或者说"硬" 当我们去使用一些 能力,我们检查一个随机 坚硬.1个 有时我们会倒霉而得到 在别处有产能的时候,我们绝对不会违反 限制的上限.
const rateLimiter = new RateLimiter(components.rateLimiter, {
llmTokens: { kind: "token bucket", rate: 40000, period: MINUTE, shards: 10 },
llmRequests: { kind: "fixed window", rate: 1000, period: MINUTE, shards: 10 },
});
在这里,我们用10个硬板处理1000个QPM. 如果你想要一些粗糙的数学 猜测要添加多少个硬块, 接受您期望的每秒最大查询 以2除以2。 每个硬体也有五(理想为十)或 增加容量。 在这种情况下,我们有10个(比率/硬度),不期望正常 流量将超过~20QPS.
提示: 如果您想要一个像 { rate: 100, period: SECOND } 而你们是
在总体周期中灵活,然后通过提高费率来降低
并按比例计算以获得足够的硬度和每个硬度的容量:
{ shards: 50, rate: 250, period: 2.5 * SECOND } 或更好:
{ shards: 50, rate: 1000, period: 10 * SECOND }。 。 。 .
注意: 同步率限制不能被硬化 .
两人的力量
我们实际上走得更远 检查两个硬盘,用一个 能力较强,能够保持相对平衡 两种技术的力量。 。 。 . 如果两者都不够 他们自己的.
重设被压抑的利率限制
重设被压抑的速率限制将会读写到所有被压抑的 如果有许多同意的请求,则可能造成OCC冲突。 若您的使用大小写 涉及频繁重播, 考虑 同步应用更新 而不是软化.
保留能力:
你也可以允许它 reserve 消除饥饿的能力
请求.
当你提前预留能力时,合同是 你可以运行你的
在指定时间运行(通过 retryAfter 字段),此时您
不必重新检查费率限额. 你的能力已经被"警告"了.
使用此操作, 您可以排队操作许多操作, 操作会以空格运行 间隔,最大限度地利用费率限额.
详细情况 堆叠站。 。 。 .
const myAction = internalAction({
args: {
//...
skipCheck: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
if (!args.skipCheck) {
// Reserve future capacity instead of just failing now
const status = await rateLimiter.limit(ctx, "llmRequests", {
reserve: true,
});
if (status.retryAfter) {
return ctx.scheduler.runAfter(
status.retryAfter,
internal.foo.myAction,
{
// When we run in the future, we can skip the rate limit check,
// since we've just reserved that capacity.
skipCheck: true,
},
);
}
}
// do the operation
},
});
添加焦躁状态
当用户同时出现太多时, 会导致网络拥堵, 数据库 以不必要的高比率消耗其他共享资源. 相反,我们可以在下一个周期内返回一个随机时间来重试。 希望这样 这是不常见的。 这种技术被指为添加"热"
简单的执行可以看起来是:
const retryAfter = status.retryAfter + Math.random() * period;
对于固定窗口,我们也通过选择起始时间来引入随机性 如果 没有提供配置. start 。 这可以防止所有客户端被淹没 午夜请求并呼唤你.
更多资源
看看这里的完整例子。 。 。 .
见 本条 更多信息 关于使用和先进模式,例如:
- 不同速率的限制策略如何在引擎盖下运作.
- 在一次交易中使用多利率限制.
- 限制匿名用户的费率.