Files
cs-note/hzh/REDIS/限流HTTP响应头.md
T

4.6 KiB
Raw Blame History

tags, create time
tags create time
http
rate-limiting
headers
standard
2026-06-01 10:30

限流 HTTP 响应头标准

概述

当请求被限流时,服务端不仅要返回 429 Too Many Requests 状态码,还需要通过 HTTP 响应头告知客户端为什么被限流、什么时候可以再试。这些标准化的头部字段是前后端协作的重要契约。

这对应你的薄弱点 Q13:区分哪些是标准的限流响应头,哪些不是。

一、三大标准限流头(RFC 7231 / Twitter API 惯例)

这三个头组成了限流响应的核心信息:

1. X-RateLimit-Limit

X-RateLimit-Limit: 100

含义: 当前时间窗口内的最大允许请求数(限流阈值)。

用途: 让客户端了解它的配额上限,用于前端 UI 展示进度条或提示文案。

2. X-RateLimit-Remaining

X-RateLimit-Remaining: 42

含义: 当前时间窗口内剩余的可用请求次数。

用途: 客户端可以据此做本地决策——如果剩余为 0,就不需要再发请求了,直接等。

3. X-RateLimit-Reset

X-RateLimit-Reset: 1690000060

含义: 当前窗口重置的 Unix 时间戳(秒级),此时 Remaining 会恢复为 Limit。

用途: 客户端计算等待时间:waitSeconds = Reset - now。

二、Retry-After 头部

除了上面三个 "信息型" 头部,还有一个 动作型 头部:

Retry-After: 5

含义: 建议客户端等待指定秒数后再重试。

这个头部在两种场景下使用:

  • 被限流时(429 响应):告诉客户端等多久再试
  • 服务繁忙时(503 响应):服务器正在维护或过载,请稍后重试

[!tip]- Retry-After vs X-RateLimit-Reset

头部 类型 单位 语义
Retry-After 动作指令 秒(整数) "请等 N 秒后再试"
X-RateLimit-Reset 信息参考 绝对时间戳 "窗口将在该时间点重置"

最佳实践: 同时返回两者。Retry-After 给简单逻辑用,Reset 给精细调度的客户端用。

三、完整响应示例

被限流时的响应

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1690000060
Retry-After: 2

{
    "error": {
        "code": "RATE_LIMITED",
        "message": "请求过于频繁,请稍后再试",
        "retry_after_seconds": 2
    }
}

正常通过的响应(也带上限流信息)

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1690000060

{
    "data": { ... }
}

[!tip]- 为什么正常响应也要带限流头?

客户端可以在不进入错误分支的情况下实时监控自己的配额消耗情况,提前做策略调整(比如把突发请求均匀分布)。

四、Q13 考点总结:哪些是标准头?

头部 是否标准 说明
✅ X-RateLimit-Limit 是 限流阈值
✅ X-RateLimit-Remaining 是 剩余配额
✅ X-RateLimit-Reset 是 重置时间戳
❌ X-RateLimit-Burst 否 非标准头,虽然有些系统会用,但不是通用规范
✅ Retry-After 是 RFC 7231 定义的 retry 头部

[!note]- 记忆技巧

记住三个关键词:Limit(总量)、Remaining(剩余)、Reset(重置) —— 这就是完整的限流信息三角。任何超出这个范围的自定义头部(如 Burst、Window、Quota 等)都是各公司自行扩展的,不属于标准规范。

五、Go 实现示例

func setRateLimitHeaders(w http.ResponseWriter, limit, remaining int64, resetTime time.Time) {
    w.Header().Set("X-RateLimit-Limit", strconv.FormatInt(limit, 10))
    w.Header().Set("X-RateLimit-Remaining", strconv.FormatInt(remaining, 10))
    w.Header().Set("X-RateLimit-Reset", strconv.FormatInt(unixMilliToSecond(resetTime.UnixMilli()), 10))
}

func setRateLimitedResponse(w http.ResponseWriter, retryAfterSec int) {
    w.Header().Set("Retry-After", strconv.Itoa(retryAfterSec))
    w.WriteHeader(http.StatusTooManyRequests) // 429
    
    json.NewEncoder(w).Encode(map[string]interface{}{
        "error": map[string]interface{}{
            "code":              "RATE_LIMITED",
            "message":           "请求过于频繁,请稍后再试",
            "retry_after_seconds": retryAfterSec,
        },
    })
}

关联笔记