限流中间件必须使用 rate.Limiter,禁用自实现计数器;需全局复用实例、设合理 burst 值、用带超时的 WaitN 排队;限流须在 bind/parse 前执行,并覆盖 OPTIONS 与健康检查路径,必要时加白名单。

限流中间件必须用 rate.Limiter,别自己写计数器
自己用 sync.Mutex + 全局变量实现限流,看似简单,实则并发不安全、无法应对突发流量、还容易阻塞 goroutine。Go 标准库的 golang.org/x/time/rate 提供了线程安全的令牌桶实现,是 Echo 限流的唯一合理起点。
-
rate.NewLimiter(10, 20)表示“每秒最多 10 个请求,最多允许积压 20 个令牌”——burst=20 是关键,设成 1 就等于把用户点击、重试全卡死 - 必须全局复用同一个
rate.Limiter实例;在 handler 里每次new一个,等于没限 - 不要只用
AllowN()返回bool然后直接return c.JSON(429, ...);这会让请求立刻失败,体验差且无法削峰
用 WaitN 阻塞排队,但必须带超时上下文
WaitN 是让超限请求排队等令牌的正确方式,但它会阻塞当前 goroutine。Echo 每个请求跑在独立 goroutine 上,所以阻塞本身没问题,但必须控制等待时长,否则客户端断连后 goroutine 可能永久挂起。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 正确做法:
ctx, cancel := context.WithTimeout(c.Request().Context(), 500*time.Millisecond),再调limiter.WaitN(ctx, 1) - 绝对不要用
limiter.WaitN(context.Background(), 1)—— 缺失取消信号,资源泄漏风险高 - 如果
WaitN返回context.DeadlineExceeded,就该返回429 Too Many Requests,而不是继续执行
中间件要覆盖 OPTIONS 和健康检查路径
限流中间件如果只 wrap 主业务路由,会漏掉两类高频请求:CORS 预检的 OPTIONS 请求,以及探针类的 /healthz、/readyz。它们不受限流影响,可能被恶意刷爆连接或绕过防护。
- 注册中间件时,确保它作用于整个
e实例,或至少覆盖所有对外暴露的分组(如e.Group("/api")) - 对明确需要放行的路径(如
/healthz),可在中间件内加白名单判断:if c.Request().URL.Path == "/healthz" { return next(c) } - 若需按 IP 或 token 区分限流,要用
sync.Map缓存 key 对应的rate.Limiter,但注意定期清理过期 key,避免内存泄漏
别在 c.Bind() 后才做限流
限流逻辑必须放在请求解析之前。如果限流中间件写在 c.Bind() 或 c.Validate() 之后,意味着每个请求都已消耗 body、解析 JSON、校验结构——此时再拒绝,CPU 和内存已经白花了。
- 限流中间件应是链中靠前的位置,理想顺序是:
logger → rateLimit → auth → bind → validate → handler - 尤其注意 multipart 文件上传场景:body 读取开销大,限流必须在任何 body 消费操作之前完成
- 如果你发现 QPS 上不去但 CPU 不高,很可能是限流位置太靠后,大量请求卡在解析阶段被丢弃

















