本文目录导读:

- 目录导读
- 限流为什么必须做在网关层?
- 四大经典限流算法对比
- Java网关限流落地案例:Spring Cloud Gateway + Redis + Lua
- 生产级进阶方案:阿里Sentinel网关流控
- 常见限流配置问答(FAQ)
目录导读
- 限流为什么必须做在网关层? —— 核心痛点与架构定位
- 四大经典限流算法对比 —— 计数器、滑动窗口、漏桶、令牌桶
- Java网关限流落地案例 —— Spring Cloud Gateway + Redis + Lua 脚本实战
- 生产级进阶方案 —— 阿里Sentinel网关流控与热点参数限流
- 常见限流配置问答(FAQ) —— 阈值设置、异常处理、动态调整
限流为什么必须做在网关层?
在微服务架构中,网关是所有请求的唯一入口(如Spring Cloud Gateway、Zuul),如果将限流分散到各个业务服务,会导致资源浪费(每个服务都要写一遍)且难以统一管控(阈值不一致、无法全局联动),网关限流能实现:
- 全局视角:统一控制QPS、并发线程数,防止单个服务被突发流量打垮后雪崩。
- 前置拦截:无效请求在入口即被丢弃,避免穿透到下游数据库或第三方接口。
- 成本可控:仅需在网关层维护一套限流组件,与业务代码完全解耦。
真实案例:某电商大促期间,下单接口突发10倍流量,若未在网关限流,订单服务数据库连接池瞬间被占满,连锁导致支付、库存服务全部超时,网关层配置
单机QPS=2000后,超出请求直接返回429 Too Many Requests,核心服务稳定运行。
四大经典限流算法对比
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定窗口计数器 | 每秒重置计数 | 实现简单 | 临界问题(窗口切换瞬间突发2倍流量) | 简单非严格限流 |
| 滑动窗口日志 | 记录每个请求时间戳,滑动统计 | 无临界问题 | 内存占用高 | 低并发环境 |
| 漏桶算法 | 请求入桶,恒定速率流出 | 绝对平滑 | 无法应对突发流量(限制了峰值) | 保护下游流量整形 |
| 令牌桶算法 | 桶内放令牌,取到才放行 | 允许一定突发,平滑控速 | 实现稍复杂 | 网关首选(如Guava RateLimiter) |
关键结论:网关限流通常采用令牌桶 + 滑动窗口组合,即用令牌桶控制平均速率,用滑动窗口防止瞬时临界值穿透,Sentinel底层正是融合了两者。
Java网关限流落地案例:Spring Cloud Gateway + Redis + Lua
1 为什么用Redis + Lua?
- 原子性:Redis执行Lua脚本是原子操作,避免并发下计数覆盖。
- 高性能:Lua脚本在Redis服务器内执行,减少网络往返(相比Java代码多次get/set)。
- 可分布式:多网关实例共享Redis,实现全局配额。
2 核心Lua脚本(令牌桶)
local key = KEYS[1] -- 用户或接口限流key
local limit = tonumber(ARGV[1]) -- 令牌桶容量
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0 -- 拒绝
else
redis.call('incr', key)
redis.call('expire', key, ARGV[2]) -- 超时自动清空
return 1 -- 放行
end
3 Java配置过滤器(伪代码)
@Component
public class RateLimitFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String clientIp = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
String key = "rate_limit:" + clientIp;
// 调用Lua脚本,判断是否放行
Long result = redisTemplate.execute(rateLimitScript,
Arrays.asList(key), "10", "60"); // 10次/60秒
if (result == 0) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}
4 配置效果测试
- 压测工具:JMeter 模拟1000个并发请求。
- 结果:前10个请求成功(200),后续请求全部返回429,Redis内key的TTL自动滑动,实现每个IP 10次/分钟的独立限额。
生产级进阶方案:阿里Sentinel网关流控
当业务复杂(需要按用户、按URL、按来源APP限流)时,手写Lua脚本已捉襟见肘。Sentinel 提供开箱即用的网关适配器:
- 精细维度:可针对
API分组、请求头、参数做流控。 - 动态规则:通过控制台实时调整阈值,无需重启网关。
- 熔断降级:限流触发后自动返回兜底结果(如JSON错误体),避免堆栈溢出。
配置示例(控制台):
资源名: /order/**
阈值类型: QPS
单机阈值: 500
流控效果: 快速失败 + 预热(默认1000毫秒内缓慢爬坡)
经验分享:大促前将阈值设为正常峰值的120%,并开启“排队等待”(漏桶模式)吸收瞬时毛刺,比简单丢弃更稳定。
常见限流配置问答(FAQ)
Q1: 限流阈值设置多少合适?
A: 通过压测获取下游应用最大可承受TPS的80%,过高则保护失效,过低则误杀正常流量,建议分阶梯:核心接口严格限,边缘接口宽松限。
Q2: 被限流的请求如何处理?
A: 最佳实践是返回 429 + Retry-After 头(告知客户端几秒后重试),并记录日志用于监控,不要静默返回空数据,否则前端可能出现空白页面。
Q3: 网关限流与业务限流冲突吗?
A: 必须设计为网关粗粒度(保护整体入口)、业务细粒度(保护具体资源)的层次结构,网关限流异常时,业务侧可降级但不重复限流。
Q4: 如果Redis宕机,限流会失效吗?
A: 必须设置本地兜底(如Guava RateLimiter缓存最近100ms的令牌),建议开启Redis高可用(哨兵/集群),并将网关本地限流阈值调整为全局的80%作为fallback。
Q5: 如何观察限流是否有效?
A: 接入监控看板(Prometheus + Grafana),统计 rejected_request_total 和 passed_request_total 两个指标,理想状态是曲线平滑无毛刺,且拒绝量占比 < 5%(说明阈值合理)。
Java网关限流是保障微服务稳定性的第一道防线,从手写Redis+Lua 到 Sentinel托管,核心是理解算法本质(令牌桶负责平滑,滑动窗口防临界),并做好多级降级(本地fallback)、监控告警、动态调参,实际项目中,建议先用简单计数器快速上线,再逐步演进到Sentinel全功能——先活下来,再活得优雅。