使用社交账号登录
固定窗口算法(Fixed Window Algorithm),又称计数器算法,是最简单、最直观的一种限流方式:把时间切成一个个连续的固定窗口,并对每个窗口内能够通过的请求数设定上限。

Fixed Window
可以把时间想象成一条被均匀切分的时间轴,例如:
在固定窗口算法中,一般会维护三个核心信息:
具体处理过程如下:
now / interval)。< 阈值:将计数器 +1,并放行请求;>= 阈值:直接拒绝请求。这样,就能保证:在每个时间窗口内,最多只会有 threshold 个请求被通过。
所谓“临界突刺”,指的是窗口交界附近的瞬时流量,可能远大于设定的限流阈值。例如:
第 0.9 秒 ~ 1.0 秒 这 100ms 内,突然来了 100 个请求,全部落在“第 0 秒这个窗口”里,被放行;第 1.0 秒 ~ 1.1 秒 这 100ms 内,又来了 100 个请求,此时已经进入“第 1 秒这个窗口”,计数器被重置,又可以全部放行。从限流规则上看,是第 0 秒窗口 100 个 + 第 1 秒窗口 100 个,规则并没有被破坏;
但从“真实时间”看,在大约 200ms 的极短时间内通过了 200 个请求,瞬时 QPS 实际上达到了 2 倍阈值,这对某些对瞬时流量非常敏感的系统来说,可能依然是不可接受的。
滑动窗口算法是在固定窗口算法基础上的改进,目标是解决“窗口边界瞬间放过两倍流量”的问题,让任意连续时间段内的请求数都更加接近设定阈值。
核心思路是:不再只看“自然时间段”(比如 10:00–10:01),而是以请求的时间点为起点,向前回溯一个固定长度的窗口(例如最近 1 秒或最近 1 分钟),统计这段时间内的请求数。
常见有两种实现方式:

1)滑动窗口计数器算法
滑动窗口计数器可以理解为“对固定窗口计数器做更细粒度的拆分和加权”:
这种方式属于“分段近似滑动”,实现比较简单,但仍然是近似值,窗口越细粒度,统计越精确,开销越大。
2)滑动窗口日志算法
滑动窗口日志算法会记录每一次请求的时间戳:
这种方式统计精度最高,是真正意义上的“任意连续窗口”限流,但需要在内存或存储中保存更多历史记录。
使用场景
优点
缺点
在 Go 生态中,滑动窗口更常见于自研实现或者基于 Redis 的服务端限流逻辑中,而通用开源库多采用令牌桶或漏桶算法作为默认实现。
令牌桶算法(Token Bucket)是最常用、最通用的限流算法之一。它通过向“桶”中按固定速率放入令牌,请求只有在获取到令牌时才被允许通过,从而控制平均处理速率,同时允许一定程度的突发流量。
在很多网关、API 网关、客户端 SDK 中,令牌桶都是默认或推荐的限流策略。

Tokenbucket
capacity 的“令牌桶”;rate 往桶中放入令牌,如果桶已满则新的令牌会被丢弃;令牌桶的关键特性是:

使用场景
优点
rate 控制平均速率,capacity 控制可承受的突发程度;缺点
golang.org/x/time/rateGo 官方扩展库 golang.org/x/time/rate 提供了一个高质量的令牌桶限流实现,使用方式非常简单:
NewLimiter(10, 20) 表示:
该库还支持按上下文阻塞等待(Wait / WaitN),常用于需要限速但不希望直接拒绝请求的场景,例如客户端对下游服务的调用。
漏桶算法(Leaky Bucket)和令牌桶一样,也是非常经典的限流与整形(shaping)算法。可以把它想象成一个“底部有孔的桶”:
请求先进入桶中排队,桶会以一个固定速率向外“漏水”,即以恒定速度处理请求。
当短时间内请求大量涌入时:

capacity 的队列(漏桶);rate 的消费者,以固定间隔从桶中取出请求并处理;与令牌桶“先发令牌、后消费”不同,漏桶更像是“先收请求、再匀速放出”。
使用场景
优点
缺点
1)go.uber.org/ratelimit:阻塞式漏桶限速
Uber 开源的 go.uber.org/ratelimit 提供了一个阻塞式漏桶限流器:
Take 会在必要时阻塞,使两次调用之间的间隔尽量稳定,从而实现“平滑限速”。这种方式适合:
2)与令牌桶库配合使用
在很多工程实践中,会组合使用:
两种算法各有侧重:令牌桶更适合控制“平均速率 + 突发”,漏桶更适合“稳定输出 + 保护下游”。
可以从几个常见维度来横向比较前面几种算法:
实现复杂度
是否允许突发流量
流量平滑程度
状态和存储开销
可以按“需求场景”来做一个粗略决策:
只是想快速给接口加一个“每秒 N 次”的粗粒度保护
需要严格意义上的“最近 N 秒/分钟”的调用频次控制
需要既控制平均速率,又希望允许业务可感知的突发
rate 贴近系统整体可承受的稳定 QPS;capacity 决定可以接住多大短暂流量峰值。极度关注下游稳定性,需要“匀速出水”保护某个资源
单机或中间件层限流:
golang.org/x/time/rate 等成熟的令牌桶实现;go.uber.org/ratelimit 做平滑节流。分布式、多实例部署的限流:
总体来说,没有一种算法在所有场景下都是“最优解”。更现实的做法是:
package main
import (
"net/http"
"golang.org/x/time/rate"
)
var limiter = rate.NewLimiter(10, 20)
func handler(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
w.WriteHeader(http.StatusTooManyRequests)
return
}
w.Write([]byte("ok"))
}
package main
import (
"fmt"
"time"
"go.uber.org/ratelimit"
)
func main() {
rl := ratelimit.New(100)
prev := time.Now()
for i := 0; i < 5; i++ {
now := rl.Take()
fmt.Println(i, now.Sub(prev))
prev = now
}
}