1. 限流基础:为什么需要限流?

在分布式系统中,限流(Rate Limiting) 是一种保护机制,用于控制单位时间内对系统资源的访问量。没有限流的系统,就像没有红绿灯的十字路口——一旦流量高峰到来,整个系统可能瞬间崩溃。

1.1 为什么需要限流?

限流的核心动机可以用一句话概括:防止系统过载,保障服务质量。具体来说,限流解决了以下三个关键问题:

  • 资源保护:数据库连接池、线程池、内存、CPU 等有限资源一旦被耗尽,系统将无法响应任何请求。
  • 公平性保障:防止某个恶意或异常客户端占用全部资源,导致其他正常用户无法访问。
  • 成本控制:在云服务场景下,无限制的流量意味着无限制的账单——限流是成本治理的第一道防线。
核心认知: 限流不是“拒绝用户”,而是“在系统容量内,优先服务最有价值的请求”。好的限流策略能让系统在过载时优雅降级,而非直接崩溃。

1.2 限流的常见场景

限流几乎出现在所有高并发系统的设计中。下表总结了三种最典型的场景及其技术特征:

场景 流量特征 限流目标 典型阈值
秒杀/抢购 瞬时峰值流量,用户量级大 保护订单系统和库存服务 QPS 限制 + 并发数限制
爬虫/机器人 高频、规律、IP 集中 识别并拦截非人类流量 IP 级别 QPS + 用户行为模式
API 防滥用 调用频率波动大,存在突发 保证 API 响应延迟稳定 令牌桶 + 熔断降级
实战提示: 在 Go 接口契约中,限流通常放在 HTTP Middleware 或 gRPC Interceptor 层实现,这样可以在请求到达业务逻辑之前就完成流量控制。

1.3 限流的核心目标

限流不是简单的“拒绝请求”,它需要达成以下四个核心目标:

  1. 容量保护:确保系统负载始终在可承受范围内,避免雪崩效应。
  2. 延迟稳定:通过控制并发请求数,防止尾延迟(Tail Latency)恶化——这是本课程的核心关注点。
  3. 公平调度:在多租户场景下,为不同客户端分配合理的资源配额。
  4. 弹性伸缩:限流阈值应能根据系统实时容量动态调整,而非固定死板。
限流核心目标与典型场景 限流 (Rate Limiting) 容量保护 延迟稳定 公平调度 弹性伸缩 秒杀/抢购 爬虫/机器人 API 防滥用 限流不是简单的“拒绝请求”,而是通过容量保护、延迟稳定、公平调度和弹性伸缩 四个目标,在秒杀、爬虫、API 防滥用等场景中保障系统可用性

1.4 限流与尾延迟的关系

本课程将限流与尾延迟(Tail Latency)紧密关联。尾延迟指的是系统中响应时间最慢的那部分请求(通常指 P99 或 P999 延迟)。限流对尾延迟的影响体现在:

  • 队列堆积:当请求超过系统处理能力时,请求会在队列中等待,导致尾延迟急剧上升。
  • 资源争抢:过高的并发会导致线程/协程频繁切换,增加调度开销,进一步恶化延迟。
  • 连锁反应:一个服务的尾延迟升高可能引发上游调用超时重试,形成流量放大效应。
关键洞察: 限流是控制尾延迟最直接的手段。在 Go 语言中,通过 channel 或 semaphore 控制并发数,可以显著降低 P99 延迟的抖动幅度。本课程后续章节将深入讲解如何通过限流契约来保障尾延迟的稳定性。
限流如何影响尾延迟 无限流:请求无节制涌入 队列无限堆积 资源争抢加剧 超时重试放大流量 尾延迟急剧恶化 限流:控制并发数 队列长度可控 资源利用率稳定 拒绝而非重试 尾延迟保持稳定 对比总结 无限流 → 尾延迟失控 限流 → 尾延迟可控 核心:用拒绝换稳定 P99 抖动降低 80%+ 系统吞吐更可预测

1.5 限流在 Go 接口契约中的位置

在 Go 微服务架构中,限流通常作为接口契约(Interface Contract)的一部分来定义。这意味着限流规则不是运维人员临时配置的,而是与服务接口一起被定义、测试和版本管理的。典型的 Go 接口契约限流定义如下:

// RateLimit 定义接口的限流契约
type RateLimit struct {
    // 每秒允许的请求数(QPS)
    RequestsPerSecond float64
    // 允许的突发请求数(Burst)
    Burst int
    // 限流策略:token_bucket | leaky_bucket | sliding_window
    Strategy string
    // 超出限流时的行为:reject | queue | degrade
    OnExceed string
}

// UserService 用户服务接口,附带限流契约
type UserService interface {
    // GetUserProfile 获取用户信息,限流:100 QPS,突发 20
    GetUserProfile(ctx context.Context, userID string) (*UserProfile, error)
    // UpdateUserProfile 更新用户信息,限流:50 QPS,突发 10
    UpdateUserProfile(ctx context.Context, user *UserProfile) error
}