ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Fluxer 限流机制深度解析:路由桶、全局桶与限流响应契约

Fluxer 限流机制深度解析:路由桶、全局桶与限流响应契约 【免费下载链接】fluxerA free and open source instant messaging and VoIP chat app built for friends, groups, and communities.项目地址https://gitcode.com/gh_mirrors/flu/fluxer点击查看免费下载Fluxer 是一个面向朋友、群组与社区的开源即时通讯与 VoIP 应用其 HTTP API 通过逐路由限流桶per-route bucket与全局限流桶global bucket双层结构约束流量。本文以 fluxer_docs/src/content/docs/topics/rate-limits.md 为骨架结合 限流中间件、限流配置聚合 与 限流服务实现 等源码完整讲解桶的作用域规则、429 响应体、X-RateLimit-*响应头、处理器内限流handler-level limits与慢速模式slowmode帮助你在接入或运维 Fluxer API 时准确理解并优雅处理限流。Buckets 与作用域Buckets and scopeFluxer 中一个 bucket 就是在一个时间窗口window内对一个允许额度allowance的计数。几乎所有路由都声明自己的 bucket超出额度的请求会被拒绝返回状态码429code为RATE_LIMITED携带一个限流响应对象携带限流响应头。路径参数占位符每个资源独立额度bucket 名称可以包含路径参数占位符写法是冒号加参数名例如guild:emojis:list::guild_id。Fluxer 在键控keying前会用请求中的实际值替换占位符因此同一个路由对每个路径资源持有独立额度。这一点在源码中有直接体现中间件中的resolveBucket遍历请求路径参数逐一替换 bucket 字符串中的:参数名占位符见 RateLimitMiddleware.tsfunction resolveBucket(bucket: string, clientId: string, ctx: ContextHonoEnv): string { let resolved bucket; const params ctx.req.param(); for (const [key, value] of Object.entries(params)) { resolved resolved.replace(:${key}, String(value)); } return ${clientId}:${resolved}; }替换后的 bucket 还会以调用者身份为前缀进行键控见下文。调用者身份键控认证、Bot、Bearer 与 IP每个 bucket 都会按调用者身份键控已认证请求按账号account与凭证类型credential kind键控OAuth2 bearer 凭证额外按所属应用owning application键控因此会话session、机器人令牌bot token、Admin API 密钥以及每个 bearer 应用对同一个账号会各自占用独立的额度。源码中getClientIdentifier精确实现了这一规则RateLimitMiddleware.tsif (user?.id) { const tokenType ctx.get(authTokenType) ?? session; if (tokenType bearer) { return user:${user.id}:bearer:${ctx.get(oauthBearerApplicationId) ?? unknown}; } return user:${user.id}:${tokenType}; }未解析出账号的请求按客户端 IP 键控IPv4 使用精确地址IPv6 使用/64前缀因此同一/64网段内的客户端会共享额度。源码中getSameIpDecisionKey负责将 IP 归一化到决策键。若部署配置为从某个请求头读取地址而请求没有携带该头Fluxer 会在评估任何 bucket之前直接以403 FORBIDDEN拒绝该请求。全局桶共享的全局额度请求还会计入全局 bucket除非其路由 bucket 声明为豁免全局桶按同一身份键控因此未解析出账号的请求会消耗其客户端 IP 的全局额度中间件中checkGlobalLimit(clientId, globalLimit)在任何路由桶检查之前执行RateLimitMiddleware.ts。豁免路由只消费自己的额度以下 bucket 是豁免的且是各自路由声明的唯一bucket它们不消耗任何全局额度webhook:execute::webhook_idwebhook:message_get::webhook_idwebhook:message_edit::webhook_idwebhook:message_delete::webhook_idwebhook:github::webhook_idwebhook:instatus::webhook_idstripe:webhook源码在 WebhookRateLimitConfig.ts 与 IntegrationRateLimitConfig.ts 中确认了这些配置例如webhook:execute::webhook_id为{limit: 60, windowMs: ms(1 minute), exemptFromGlobal: true}stripe:webhook为{limit: 300, windowMs: ms(1 minute), exemptFromGlobal: true}。此外user:group_dm:create与user:group_dm:recipient:add两个 bucket 也是豁免的但它们是某路由上的第二个 bucket第一个 bucket 不豁免因此这两个路由仍然会消耗全局额度。源码中可见user:group_dm:create配置为{limit: 10, windowMs: ms(1 hour), exemptFromGlobal: true}UserRateLimitConfig.ts。全局窗口与默认额度全局窗口为1 秒默认额度为每秒 50 次请求持有HIGH_GLOBAL_RATE_LIMIT账号标记account flags 文档见 fluxer_admin 的账号标记实现的账号获得每秒 1200 次持有RATE_LIMIT_BYPASS标记的账号豁免于全局桶与所有路由桶其成功响应不携带任何限流头。上述常量在源码中都有对应实现RateLimitMiddleware.tsif (user?.flags (user.flags UserFlags.HIGH_GLOBAL_RATE_LIMIT) ! 0n) { return 1200; } return 50;同时RateLimitService中DEFAULT_GLOBAL_WINDOW_MS 1000RateLimitService.ts也印证了 1 秒全局窗口。两个容易混淆的注意点:::note[An allowance drains continuously] 每个 bucket 都是漏桶leaky bucket一次性最多允许声明的额度并在每个声明窗口内以该额度持续补充refill。因此客户端耗尽额度后只要桶中补充出足够额度即可再次发送。 ::::::note[The headers and the body report different instants]X-RateLimit-Reset与X-RateLimit-Reset-After报告的是桶完全排空的时刻而响应体中的retry_after报告的是再次准入一个请求所需等待的较短延迟。 ::::::note[A 429 can hide a 401 or 403] 一个被限流的请求即使其凭证本应被以401或403拒绝也可能返回429。测试 GlobalRateLimitRevokesSession.test.ts 正好验证了这一点会话被全局限流吊销后再次请求返回 401UNAUTHORIZED而非 429。 :::第二个路由桶与计数时机部分路由声明了第二个路由桶包括群组私信创建group DM creation、添加群组成员adding group recipients以及删除 guild 表情/贴纸deleting guild emoji or stickers。Fluxer 在处理器运行之前就对请求进行路由桶计数所以被处理器拒绝的请求仍然会消耗额度。警告全局拒绝会吊销用户会话:::caution[A global denial revokes a user session] 当全局桶拒绝了由非 Bot 账号的用户会话令牌认证的请求时Fluxer 会在写回 429之前吊销该令牌客户端必须重新认证。Bot 令牌、OAuth2 访问令牌与 Admin API 密钥绝不会被这样吊销路由桶拒绝也绝不会吊销任何凭证。 :::源码中的revokeAuthenticatedSessionOnGlobalRateLimit精确实现了这一行为仅当authTokenType session且用户非 Bot 时调用AuthSession.revokeTokenRateLimitMiddleware.ts并由测试 GlobalRateLimitRevokesSession.test.ts 验证。通过实例配置整体关闭部署可以通过实例配置同时禁用两类桶。禁用期间没有任何响应携带X-RateLimit-*头不会有请求以429 RATE_LIMITED被拒绝该开关同时关闭登录额度login allowances其他所有处理器内限流仍然生效。中间件中shouldEnforceRateLimits读取Config.dev.disableRateLimitsRateLimitMiddleware.ts测试模式下还支持通过x-fluxer-test-enable-rate-limits请求头开启、x-fluxer-test-global-rate-limit覆盖全局额度便于测试验证。限流响应对象Rate limit response object拒绝响应体包含普通错误响应的全部成员并额外增加两个成员。结构字段类型描述code1stringbucket 拒绝时为RATE_LIMITEDmessage2string本地化的限流提示消息globalboolean拒绝是否由全局桶产生路由桶拒绝时存在且为falseretry_after3number距离下一个请求被准入的延迟小数秒1在路由桶中间件之外强制执行的限流可以复用该响应体但使用自己的 code。Send phone verification 是目前唯一活跃的例子返回PHONE_RATE_LIMIT_EXCEEDED。2使用为请求解析出的本地化locale账号设置优先于 Accept-Language。3永不低于 0.001当没有计算出小数延迟时回退为整秒的Retry-After值。RateLimitService中retryAfterDecimal: result.retryAfterMs 0 ? Math.max(0.001, retryAfterDecimal) : undefined正是这一规则的实现RateLimitService.ts。Resend IP authorisation 的冷却期以自己的 code 和不同的响应体回答 429具体内容见下文回答 429 的额度。慢速模式slowmode 的拒绝则回答400 SLOWMODE_RATE_LIMITED。示例{ code: RATE_LIMITED, message: Youre being rate limited., global: false, retry_after: 0.428 }测试 GlobalRateLimitRevokesSession.test.ts 验证了全局拒绝时json.global true、X-RateLimit-Global头为true、X-RateLimit-Scope为global且X-RateLimit-Bucket头为null。限流作用域Rate limit scopesX-RateLimit-Scope头表示产生拒绝的作用域。值描述user拒绝来自仅按调用者键控的额度global拒绝来自全局桶shared1拒绝来自多个账号可相互耗尽的额度1没有任何路由桶声明自己的作用域所以每个路由桶拒绝都报告user。Send phone verification 是唯一现实存在的shared来源当每个号码的发送额度或号码级 provider 冷却产生拒绝时两者都按提交的号码键控因此两个账号向同一个号码发送会共享额度。在中间件实现中路由桶拒绝使用routeConfig.scope ?? useremail bucket 拒绝使用scope: sharedRateLimitMiddleware.ts。限流响应头Rate limit headers这些头描述一次限流决策。返回 429 的操作会携带这组头。字段类型描述Retry-After?1string距下一次请求被准入的延迟整秒向上取整永不小于 1X-RateLimit-Scope?1string产生拒绝的限流作用域X-RateLimit-Global?2string全局 HTTP 429 时的字面值trueX-RateLimit-Limit?3string路由额度一次性最多允许的请求数X-RateLimit-Remaining?3string路由额度仍允许的请求数拒绝时恒为 0X-RateLimit-Reset?34string路由额度完全恢复可用的 Unix 秒级时间戳X-RateLimit-Reset-After?35string路由额度完全恢复可用还需的秒数X-RateLimit-Bucket?36string路由桶的稳定 16 位十六进制标识符1在所有限流拒绝时发送且绝不会出现在成功响应上。2仅在全局桶产生拒绝时发送此时不发送任何路由桶头。3在路由 HTTP 429 时发送X-RateLimit-Bucket受注 6 约束。在成功响应上仅当凭证解析为Bot 账号、或请求未解析出账号但位于同时具备webhook_id与token路径参数的路由时才会发送。4值若早于或等于当前秒会被替换为下一秒。5成功响应上四舍五入到毫秒精度并去除尾部零拒绝时输出精确计算的小数。6是路由 bucket 的不透明标识符不代表调用者。从源码看setRateLimitHeaders通过createHash(sha256).update(bucket).digest(hex).slice(0, 16)生成桶哈希RateLimitMiddleware.ts与测试中对X-RateLimit-Bucket的断言完全一致。处理器内强制执行的限流返回 429 时携带其他路由头但没有X-RateLimit-Bucket。:::note[Cross-origin clients cannot read rate-limit headers] 跨域策略cross-origin policy 不会暴露这些头。请改用响应体中的retry_after值。 :::处理器内限流Limits enforced inside a handler处理器内强制的额度独立于路由桶与全局桶键控因此即使两类桶仍有空间耗尽该额度也会拒绝请求。以下是完整清单。disable_rate_limits部署开关会连同两类桶一起关闭登录额度relax_registration_rate_limits关闭注册额度。其余所有额度在所有部署上都会强制执行。拒绝有两种形态回答 429 的额度携带限流响应对象与限流头减去X-RateLimit-Bucket回答 400 的额度回答400 INVALID_FORM_BODY并附带一个code指向被耗尽额度的校验错误条目。400 形态没有retry_after成员、X-RateLimit-*头与Retry-After头剩余延迟只出现在条目的本地化message中。回答 429 的额度Allowances answering 429操作额度错误码Log in with a password每 15 分钟 5 次按提交的邮箱地址键控RATE_LIMITEDLog in with a password每 30 分钟 10 次按客户端 IP 键控IPv4 精确、IPv6 按/64RATE_LIMITEDRegister an account每 15 分钟 3 次按提交的邮箱键控RATE_LIMITEDRegister an account每小时 3 次按客户端 IP 键控RATE_LIMITEDRegister an account每小时 15 次按客户端子网键控IPv4/24或 IPv6/48RATE_LIMITEDResend email verification每 15 分钟 3 次按账号存储的邮箱键控RATE_LIMITEDRequest password recovery每 30 分钟 20 次按客户端 IP 键控RATE_LIMITEDRequest password recovery每 30 分钟 5 次按提交的邮箱键控RATE_LIMITEDStart email change 与 Resend original email code每 15 分钟 3 次发送按已认证账号键控RATE_LIMITEDRequest new email、Resend new email code 与两种 bounced email recovery 发送每 15 分钟 5 次发送按已认证账号键控RATE_LIMITEDStart password change每 15 分钟 3 次发送按已认证账号键控RATE_LIMITEDResend password change code每 15 分钟 3 次发送按已认证账号键控RATE_LIMITED邮箱/密码变更 ticket 上的每次 code 重发与每次新地址请求每 30 秒 1 次发送按 ticket 键控并从其上一次发送起算RATE_LIMITEDReport message、Report user、Report guild 与 Create DSA report每小时 5 次按举报人键控账号或已验证邮箱RATE_LIMITEDReport message每小时 3 次按举报人与频道共同键控RATE_LIMITEDReport message每小时 20 次按被举报消息键控跨所有举报人RATE_LIMITEDReport message每小时 4 次按举报人与 guild 共同键控guild 消息RATE_LIMITEDSend phone verification每 6 小时 3 次按已认证账号键控PHONE_RATE_LIMIT_EXCEEDEDSend phone verification每 5 天 3 次按提交的号码键控PHONE_RATE_LIMIT_EXCEEDEDResend IP authorisationticket 签发后 30 秒内无任何操作按授权 ticket 键控IP_AUTHORIZATION_RESEND_COOLDOWNSMS provider 节流可能施加额外冷却返回PHONE_RATE_LIMIT_EXCEEDED并携带剩余延迟。Resend IP authorisation 冷却没有X-RateLimit-*头只有整秒的Retry-After头且响应体以顶层resend_available_in与retry_after再次报告该延迟。同一 ticket 上的第二次重发返回400 IP_AUTHORIZATION_RESEND_LIMIT_EXCEEDED。该额度永不补充且 ticket 在签发 15 分钟后过期。回答 400 的额度Allowances answering 400操作额度校验错误码Modify current user结果用户名或判别符每 3 小时 5 次USERNAME_CHANGED_TOO_MANY_TIMESModify current user个人简介每 30 分钟 25 次提交值不同时BIO_CHANGED_TOO_MANY_TIMESModify current user代词每 30 分钟 25 次提交值不同时PRONOUNS_CHANGED_TOO_MANY_TIMESModify current user强调色每 30 分钟 25 次提交值不同时ACCENT_COLOR_CHANGED_TOO_MANY_TIMESModify current user任何非 null 头像每 30 分钟 25 次AVATAR_CHANGED_TOO_MANY_TIMESModify current user通过权益检查后的任何横幅值每 30 分钟 25 次BANNER_CHANGED_TOO_MANY_TIMESUpdate bot profileBot 结果用户名或判别符每 3 小时 5 次USERNAME_CHANGED_TOO_MANY_TIMESModify current guild memberguild 头像每 30 分钟 25 次无论何时提供AVATAR_CHANGED_TOO_MANY_TIMESModify current guild memberguild 横幅每 30 分钟 25 次无论何时提供BANNER_CHANGED_TOO_MANY_TIMESModify current guild memberguild 个人简介每 30 分钟 25 次提交值不同时BIO_CHANGED_TOO_MANY_TIMESModify current guild memberguild 代词每 30 分钟 25 次提交值不同时PRONOUNS_CHANGED_TOO_MANY_TIMESModify current guild memberguild 强调色每 30 分钟 25 次提交值不同时ACCENT_COLOR_CHANGED_TOO_MANY_TIMESModify voice activity sharing分享默认值每 24 小时 1 次VOICE_ACTIVITY_SHARING_ON_COOLDOWNComplete login with TOTP 与 Complete login with WebAuthn MFA每 15 分钟 10 次多因素尝试INVALID_CODEComplete login with TOTP 与 Complete login with WebAuthn MFA单个 MFA ticket 上每 5 分钟 5 次多因素尝试INVALID_CODESudo modetotp方法每 15 分钟 10 次多因素尝试INVALID_MFA_CODE键控归属每个 Modify current user 额度按已认证账号键控Bot 标签额度按 Bot 账号键控——因此所有者修改 Bot 标签会消耗 Bot 的额度。guild 成员额度按guild 与成员共同键控一个账号在每个 guild 中持有独立额度。登录额度分别按账号与 MFA ticket 键控sudo 额度按账号键控。关键行为Fluxer 在检查 code 之前就消耗每个多因素额度因此用正确 code 撞上已耗尽额度时报告结果与错误 code 完全一致。正确 code 会清空计数器。ticket 额度在拒绝时还会销毁 MFA ticket客户端需从 Log in with a password 重新开始。两种形态都不回答的额度Allowances answering neither shapeGet desktop handoff information 与 Complete desktop handoff共享一个按客户端 IP 键控的失败尝试计数器5 次失败会让两个操作被阻塞 15 分钟自最近一次失败起算被阻塞的请求返回400 INVALID_HANDOFF_CODE顶层 code无校验条目。Get desktop handoff information 还单独允许每个 handoff code 进行 3 次成功查询第 4 次同样返回该顶层 code。Refund latest purchase允许每个账号每 30 天一次自助退款窗口内的请求返回403 STRIPE_REFUND_COOLDOWN_ACTIVE。Slowmode同样在处理器内强制执行。慢速模式Slowmode慢速模式限制一个账号在一个频道内发送消息的频率。Fluxer 将拒绝报告为普通请求失败被拒绝的发送返回400 SLOWMODE_RATE_LIMITED携带顶层小数秒retry_after与整秒Retry-After头。响应没有X-RateLimit-*头客户端可通过状态码与 code 将其与桶拒绝区分。额度为频道在rate_limit_per_user中配置的每个时间间隔一条消息按每个账号与频道对分别计数仅对在 guild 频道中发送、且配置间隔大于零的非 Bot 账号计数持有 BYPASS_SLOWMODE 权限的调用者豁免Get channel slowmode state 会报告调用者在尝试发送前的剩余延迟。源码中消息发送服务在发送前检查慢速模式slowmodeKey \slowmode:${channelId}:${user.id}并通过checkLimit检查[MessageSendService.ts](https://link.gitcode.com/i/e9cbedf17c4f07152c19798637a875fc)频道接口也暴露了get_channel_slowmode_state[ChannelController.ts](https://link.gitcode.com/i/ada8d5d0528cde497fe9e15a5bc82d7c)。测试 [SlowmodeEnforcement.test.ts](https://link.gitcode.com/i/69c8645d3876eef2b8ce1367836c5e1e) 验证了发送成功后才消耗慢速额度、以及超限返回400 SLOWMODE_RATE_LIMITED 的行为。其他协议面Other surfaces每个协议面都有独立的限流契约主 Gateway其会话、命令、重放replay、背压backpressure与准入admission限制见 Gateway limits and rate limitsMedia Proxy API没有请求计数限流通过并发度、负载与截止时间限制约束工作上传中继upload relay每次传输都用会过期的上传 URL授权并限制请求体大小。小结客户端与运维的实践要点区分桶拒绝与处理器内拒绝429 X-RateLimit-*头 retry_after是桶拒绝400SLOWMODE_RATE_LIMITED、400INVALID_FORM_BODY带指向额度的校验码等是处理器内限流。用响应体retry_after而不是响应头做退避跨域客户端无法读取X-RateLimit-*头见跨域策略。注意 429 会掩盖 401/403凭证无效的请求也可能因限流返回 429不要仅凭状态码判断认证失败。全局拒绝会吊销用户会话非 Bot 用户会话令牌被全局桶拒绝后必须重新认证Bot 令牌与 OAuth2 令牌不受影响。额度是持续补充的漏桶耗尽后等待retry_after准入一个请求或X-RateLimit-Reset-After完全恢复即可继续。IP 与子网键控同一 IPv6/64或注册场景的/48子网内的客户端共享额度NAT/代理场景请留意共享影响。围绕上述机制的完整配置聚合位于 RateLimitConfig.ts它合并了 Auth、OAuth、User、Channel、Guild、Webhook、Admin 等 12 个模块的限流段核心执行逻辑在 RateLimitMiddleware.ts底层漏桶服务在 RateLimitService.ts可供继续深入阅读。赞分享【免费下载链接】fluxerA free and open source instant messaging and VoIP chat app built for friends, groups, and communities.项目地址https://gitcode.com/gh_mirrors/flu/fluxer点击查看免费下载相关推荐如何快速上手DeepSeek-R1-Distill-Qwen-7BAMD NPU部署的完整指南如何快速上手DeepSeek R1 Distill Qwen 7BAMD NPU部署的完整指南 DeepSeek R1 Distill Qwen 7B_raiApache Pulsar 限流重构PIP-322AsyncTokenBucket 无锁令牌桶与统一发布限流机制深度解析Apache Pulsar 限流重构PIP 322AsyncTokenBucket 无锁令牌桶与统一发布限流机制深度解析 Apache Pulsar 的限消息队列后端实战解析5个高效使用Python通达信数据接口的核心技巧实战解析5个高效使用Python通达信数据接口的核心技巧 Python通达信数据接口为金融数据分析师和量化交易开发者提供了免费、高效且专业的数据获取解决方案。金融科技数据分析上一篇【告别Python依赖】SmartJavaAIJava开发者的离线AI算法工具箱完全指南下一篇【限时免费】 SmartJavaAI 人脸识别技术详解与实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表