一、限流基础概念
1.1 什么是限流
限流(Rate Limiting / Traffic Shaping)是一种通过控制单位时间内允许通过的请求数量、并发连接数或数据传输速率,来保护系统不被突发流量冲垮的防护手段。其核心思想是:在系统容量边界内,有选择地拒绝或延迟部分请求,确保核心服务对大多数用户仍然可用。
限流本质:用一部分请求的失败(或被延迟),换取整体系统的稳定与低延迟。
限流通常作用于以下三个维度:
| 维度 | 控制对象 | 典型指标 | 示例 |
|---|---|---|---|
| 速率(Rate) | 单位时间内的请求次数 | QPS、RPS、TPS | 每秒最多处理 1000 次 API 调用 |
| 并发(Concurrency) | 同时处理中的请求数 | 活跃连接数、goroutine 数 | 最多同时处理 200 个 WebSocket 连接 |
| 容量(Capacity) | 累积可突发的工作量 | 令牌桶容量、队列深度 | 突发时可积压 500 个任务到缓冲队列 |
1.2 为什么需要限流
在分布式系统中,限流不是可选项,而是保障服务稳定性的必选项。以下四个场景最能说明限流的必要性:
- 防止资源耗尽:数据库连接池、线程池、内存、CPU 等资源都有上限。无限制的请求会迅速耗尽资源,导致服务完全不可用。
- 隔离故障扩散:单个服务的过载可能通过 RPC 调用链扩散到上游和下游,形成雪崩效应。限流在源头切断过载传播。
- 保障公平使用:在多租户系统中,一个租户的突发流量不应挤占其他租户的服务容量。限流实现租户级别的资源隔离。
- 控制成本:云服务按量计费,无限制的流量会导致不可控的账单。限流将资源消耗控制在预算范围内。
压测视角:在性能压测中,限流点往往是系统的瓶颈定位锚点。通过观察限流触发时的 QPS 和延迟分布,可以精确推断系统的真实容量上限。
1.3 限流与熔断降级的关系
限流、熔断、降级是分布式系统稳定性的三大支柱,它们既有明确分工,又需要协同配合。以下表格清晰对比三者的核心差异:
| 机制 | 触发条件 | 动作 | 恢复方式 | 典型场景 |
|---|---|---|---|---|
| 限流 | 请求速率/并发超过阈值 | 拒绝或延迟请求 | 流量回落到阈值以下后自动恢复 | 秒杀、热点 Key、突发流量 |
| 熔断 | 错误率/延迟超过阈值(通常基于滑动窗口统计) | 快速失败,停止调用下游 | 经过冷却时间后尝试半开探测 | 下游服务故障、网络分区 |
| 降级 | 资源紧张或依赖故障 | 关闭非核心功能,返回降级结果 | 人工或自动恢复 | 大促时关闭评论、推荐等非核心服务 |
常见误区:很多团队将限流和熔断混为一谈。限流是主动预防,在系统健康时就开始控制;熔断是被动保护,在系统已经出现故障时切断调用链。两者必须同时部署,不能相互替代。
1.4 限流在 Go 服务中的典型位置
在 Go 微服务架构中,限流可以部署在多个层级,每个层级解决不同的问题:
- 网关层(如 Kong、APISIX、自研网关):全局流量控制,基于 IP、Token、路由进行限流。
- 中间件层(如 Gin 中间件、gRPC 拦截器):服务级别的限流,针对特定接口或方法。
- 业务逻辑层:精细化限流,例如针对用户 ID、商品 ID 等业务 Key 进行限流。
- 资源访问层(数据库、缓存、消息队列):保护底层资源不被过度使用。
Go 限流实战要点:Go 标准库
golang.org/x/time/rate 提供了基于令牌桶的限流实现;对于更复杂的场景(如自适应限流、分布式限流),可以使用 github.com/uber-go/ratelimit 或基于 Redis + Lua 的自定义方案。
1.5 限流与尾延迟的关系
限流对尾延迟(Tail Latency,即 P99/P999 延迟)有直接影响:
| 限流策略 | 对尾延迟的影响 | 说明 |
|---|---|---|
| 无限流 | 尾延迟急剧升高 | 请求排队堆积,P99 延迟从 10ms 飙升到 10s+ |
| 固定速率限流 | 尾延迟稳定在可控范围 | 超出容量的请求被拒绝,剩余请求延迟正常 |
| 自适应限流 | 尾延迟最优 | 根据实时延迟动态调整限流阈值,在吞吐和延迟之间取得平衡 |
| 排队 + 超时 | 尾延迟受队列深度控制 | 队列满时拒绝,队列内请求延迟可控 |