Skip to content
bitzorcas
中EN

Reference

配额与限流:Host 令牌桶与计费配额

区分 Host 层 Redis 令牌桶限流与 PlatformBilling 计费配额两套机制:429 与超额的归属、QuotaService 检查、用量提交命令与幂等事实。

Last updated

多租户平台要同时回答两个问题:“这个请求会不会压垮系统”和”这个客户有没有为这次调用付费”。它们由两套独立机制回答,混为一谈是常见的架构错误:

  • 嘈杂邻居靠 Host 层限流兜底:Redis 令牌桶在网关侧短路洪峰;
  • 超出套餐靠 PlatformBilling 配额检查计量:按套餐余量决定放行还是拒绝。

BitzOrcas 没有一个横切所有消息的”配额管道”。Mediator 管线里不存在配额拦截行为;[QuotaConsumer] 之类的声明特性也不存在。拦截点只有两处:Host 中间件层与显式的配额服务调用。

两套机制的分工

令牌耗尽放行OverQuotaBehavior=RejectAllowAndRecord

入站请求

Host 限流层
RedisTokenBucket / 滑动窗口

429 Too Many Requests

业务 Handler

计费配额
QuotaService.Check(Plan, UsageMeter, ...)

配额错误 → 403 语义

执行并记录用量

关注点归属典型信号
防刷、防雪崩Host RateLimitingHTTP 429
套餐内调用次数PlatformBilling 配额业务结果里的配额错误
用量对账UsageMeter.Record/api/platform-billing/usage

第一步:Host 限流

Api 组合根默认启用限流(RateLimiting:Enabled=true)。RedisTokenBucketRateLimiter 以 Redis 令牌桶实现跨实例一致的全局速率约束,分区策略还包含滑动窗口与固定窗口变体。令牌不足时直接返回 429,请求根本不会进入 Mediator 管线。

第二步:计费配额检查

套餐配额不挂在管道上,而是作为业务语义在应用层显式检查。QuotaService.Check 对照当前套餐与 UsageMeter 余量给出结论:

执行前检查套餐余量
// Plan 为当前租户生效套餐;UsageMeter 是权威用量事实聚合。
var check = quotaService.Check(plan, usageMeter, meterCode: "reporting.advanced_export", quantity: 1);
// 配额不足不是异常:它是可预期的业务拒绝,走稳定错误码而不是抛异常。
if (check.IsFailure)
return Result.Failure<string>(check.Error);
生成用量记录
// usageId 由调用方分配,重复提交同一 usageId 不产生第二条事实。
var record = RecordUsageCommand.Create(
tenantId: currentTenant.Tenant.EffectiveTenantId,
usageId: usageId,
meterCode: "reporting.advanced_export",
quantity: 1,
occurredAt: clock.UtcNow);

RecordUsageCommand 经 [GenerateEndpoint(HttpRoute.Post, "/api/platform-billing/usage")] 暴露,要求 platform-billing.usage.create 权限;最终由 UsageMeter.Record(usageId, quantity, occurredAt) 把次数写入聚合。

总结

  • 429 永远来自 Host 限流层;套餐超额表现为业务层的配额错误结果。前端据此分流重试策略,不要把两者都当服务器故障;
  • 配额治理的原子性在数据库(用量聚合的条件更新),不在管道;这保证了用量事实与业务事务各自独立可审计;
  • 如果需要按 Feature 维度做运行时开关,那是 License Feature 或 Tenant Feature Entitlement 的职责域,不属于本页机制。

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%