---

title: API网关速率限制与配额策略(租户级/用户级)最佳实践

keywords:

  • 速率限制
  • 配额
  • 滑动窗口
  • 令牌桶
  • 租户限流
  • 用户限流
  • 路由维度
  • 突发控制
  • 持续速率
  • 观测与审计

description: 构建租户级与用户级的速率限制与配额策略,结合滑动窗口与令牌桶实现突发与持续速率控制,附可验证的限流引擎与路由整合示例。

categories:

  • 文章资讯
  • 技术教程

---

一、目标与参数

  • 支持租户、用户与路由维度的组合Key;最小窗口与上限参数可验证。
  • 突发与持续双策略:滑动窗口用于峰值限制,令牌桶用于持续速率。

二、滑动窗口

class SlidingWindowLimiter {
  windowMs: number
  limit: number
  hits = new Map<string, number[]>()
  constructor(windowMs: number, limit: number) { this.windowMs = windowMs; this.limit = limit }
  allow(key: string): { ok: boolean; remaining: number } {
    const now = Date.now()
    const arr = (this.hits.get(key) || []).filter(t => now - t < this.windowMs)
    if (arr.length >= this.limit) return { ok: false, remaining: 0 }
    arr.push(now)
    this.hits.set(key, arr)
    return { ok: true, remaining: Math.max(0, this.limit - arr.length) }
  }
}

三、令牌桶

class TokenBucketLimiter {
  capacity: number
  refillPerSec: number
  buckets = new Map<string, { tokens: number; ts: number }>()
  constructor(capacity: number, refillPerSec: number) { this.capacity = capacity; this.refillPerSec = refillPerSec }
  allow(key: string): { ok: boolean; tokens: number } {
    const now = Date.now()
    const b = this.buckets.get(key) || { tokens: this.capacity, ts: now }
    const elapsed = (now - b.ts) / 1000
    b.tokens = Math.min(this.capacity, b.tokens + elapsed * this.refillPerSec)
    b.ts = now
    if (b.tokens < 1) { this.buckets.set(key, b); return { ok: false, tokens: Math.floor(b.tokens) } }
    b.tokens -= 1
    this.buckets.set(key, b)
    return { ok: true, tokens: Math.floor(b.tokens) }
  }
}

四、组合策略与路由整合

type Req = { headers: Record<string, string | undefined>; path: string }
type Res = { status: (n: number) => Res; end: (b?: string) => void; setHeader: (k: string, v: string) => void }

class CompositeLimiter {
  win: SlidingWindowLimiter
  bucket: TokenBucketLimiter
  constructor(win: SlidingWindowLimiter, bucket: TokenBucketLimiter) { this.win = win; this.bucket = bucket }
  allow(key: string): boolean {
    const a = this.win.allow(key)
    if (!a.ok) return false
    const b = this.bucket.allow(key)
    return b.ok
  }
}

function keyOf(req: Req): string {
  const tenant = req.headers['x-tenant-id'] || 'anon'
  const user = req.headers['x-user-id'] || 'anon'
  return `${tenant}:${user}:${req.path}`
}

function rateGuard(limiter: CompositeLimiter) {
  return function handler(req: Req, res: Res, next: Function) {
    const key = keyOf(req)
    if (!limiter.allow(key)) {
      res.setHeader('Retry-After', '1')
      return res.status(429).end('rate_limited')
    }
    next()
  }
}

五、配额管理

class QuotaStore {
  counts = new Map<string, number>()
  limit: number
  constructor(limit: number) { this.limit = limit }
  add(key: string, n: number): boolean {
    const v = (this.counts.get(key) || 0) + n
    if (v > this.limit) return false
    this.counts.set(key, v)
    return true
  }
  remaining(key: string): number { return Math.max(0, this.limit - (this.counts.get(key) || 0)) }
}

function quotaGuard(store: QuotaStore) {
  return function handler(req: Req, res: Res, next: Function) {
    const key = keyOf(req)
    const ok = store.add(key, 1)
    if (!ok) return res.status(403).end('quota_exceeded')
    next()
  }
}

六、验收清单

  • 最小窗口windowMs≥1000与滑动上限通过;令牌桶容量与补充速率生效。
  • 租户/用户/路由组合Key验证;速率限制返回429并包含Retry-After
  • 配额计数可靠并可查询剩余;审计记录包含组合Key与路由信息。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部