清晨,地铁早高峰,人流瞬间超过承载极限,车厢门无法关闭,系统几近瘫痪。
突然,你的手机传来连续的告警:“服务响应超时”、“线程池满载”、“数据库连接耗尽”——线上系统正经历着同样的灾难。流量洪峰瞬间冲垮资源防线,服务崩溃,业务停滞。这一切,往往只是因为缺少一个关键的防御机制:限流。
限流如同地铁站的闸机,控制系统入口的流量,确保核心服务在压力下依然存活。它不是为了让系统更快,而是为了保证系统在最糟糕的情况下,依然能够提供基本可用的服务。
一、理解限流:系统的“流量控制阀”
想象一家每日产能100杯的奶茶店。面对500名顾客的突然涌入,最佳策略并非照单全收,而是服务好前100位,确保质量和体验,对后续顾客进行友好劝退。
系统限流(Rate Limiting)同理,它是一种保护性机制,通过控制单位时间内的请求处理数量(如TPS),防止系统因突发或恶意流量过载而崩溃。超限的请求会被拒绝或排队。
二、为什么必须实施限流?
- 防止雪崩效应:突发流量(如秒杀、爬虫攻击)会迅速耗尽CPU、内存、数据库连接等资源,导致服务宕机,并可能引发依赖链上的连环故障。
- 保障核心业务:在资源紧张时,优先确保登录、支付等关键路径的畅通,对非核心功能进行限流或降级。
- 抵御恶意攻击:有效抑制脚本发起的暴力破解、接口刷取等恶意行为。
- 保证服务质量(QoS):在高负载下,为大多数用户提供稳定、可预测的响应体验。
💡 核心价值:限流的目的并非拒绝用户,而是为了在极端情况下更好地服务用户。
三、Java生态中的主流限流方案
方案一:Guava RateLimiter(单机限流)
Google Guava库提供的RateLimiter基于令牌桶算法,实现简单,适用于单JVM应用。
import com.google.common.util.concurrent.RateLimiter;
public class SimpleRateLimiter {
// 每秒生成10个令牌
private static final RateLimiter limiter = RateLimiter.create(10.0);
public boolean tryAcquire() {
return limiter.tryAcquire(); // 尝试获取令牌,非阻塞
}
public void handleRequest() {
if (tryAcquire()) {
// 执行业务逻辑
System.out.println("请求处理成功");
} else {
// 触发限流处理
System.out.println("请求过于频繁,请稍后重试");
}
}
}
✅ 优点:简单易用,零外部依赖。
❌ 缺点:仅适用于单机环境,无法在分布式集群中实现全局限流。
方案二:Redis + Lua脚本(分布式限流)
在微服务架构中,需要跨实例共享限流状态,Redis 是理想的选择。结合Lua脚本可保证计数操作的原子性。
// 基于Spring Data Redis的示例
@Autowired
private StringRedisTemplate redisTemplate;
public boolean isAllowed(String key, int maxRequests, int windowSeconds) {
String luaScript = """
local current = redis.call('get', KEYS[1])
if current and tonumber(current) >= tonumber(ARGV[1]) then
return 0
end
redis.call('incr', KEYS[1])
redis.call('expire', KEYS[1], ARGV[2])
return 1
""";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList("rate_limit:" + key),
String.valueOf(maxRequests),
String.valueOf(windowSeconds)
);
return result != null && result == 1;
}
✅ 优点:支持分布式环境,精度高。
❌ 缺点:引入Redis 依赖,增加了系统复杂度。
方案三:Sentinel(生产级流量治理)
阿里巴巴开源的Sentinel功能全面,支持QPS限流、并发线程数控制、熔断降级、热点参数限流等,并提供实时的监控控制台。
// 1. 使用注解定义资源
@SentinelResource(value = "createOrder", blockHandler = "handleFlowLimit")
public String createOrder() {
return "订单创建成功";
}
// 2. 定义限流处理函数
public String handleFlowLimit(BlockException ex) {
return "系统繁忙,请稍后再试";
}
// 3. 通过代码动态配置规则(通常在初始化阶段)
private void initFlowRules() {
FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(5); // 阈值:每秒5次
FlowRuleManager.loadRules(Collections.singletonList(rule));
}
✅ 优点:功能强大,支持集群流控,生态完善。
✅ 推荐用于生产环境的 Java 微服务。
方案四:网关层统一限流(如Spring Cloud Gateway)
在微服务架构的入口网关处进行限流,可以保护所有下游业务服务。Spring Cloud Gateway内置了基于Redis的请求限流器。
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒填充的令牌数
redis-rate-limiter.burstCapacity: 20 # 令牌桶总容量
key-resolver: "#{@ipKeyResolver}" # 按IP限流
✅ 优点:策略集中管理,与业务代码解耦,对后端服务无侵入。
四、核心限流算法原理解析
1. 固定窗口计数器
将时间划分为固定时长窗口(如1秒),每个窗口内请求数不能超过阈值。问题在于存在“窗口临界突变”问题,例如在上一秒末尾和下一秒开头瞬间涌入双倍流量。
2. 滑动窗口
记录滑动时间窗口(如最近1秒)内的所有请求。新请求到来时,移除超出窗口的旧记录,再判断当前数量。解决了临界突变,但需要存储更多时间戳数据。
3. 漏桶算法
请求像水一样流入桶中,桶以恒定速率(处理能力)“漏水”。如果桶满,则新请求溢出(被拒绝)。优点是输出流量绝对平滑;缺点是无法应对突发流量,即使桶是空的,流出速率也是固定的。

4. 令牌桶算法 ⭐(最常用)
系统以恒定速率向桶中放入令牌。请求处理前需先获取一个令牌。桶有最大容量,允许短时间内消费累积的令牌,从而支持突发流量。当桶空时,请求被限流。Guava RateLimiter、Nginx及许多云原生 网关均采用此算法。
令牌桶示例:
- 配置:生成速率=10个/秒,桶容量=20个。
- 常态:每秒稳定处理约10个请求。
- 突发:若之前无请求,桶内累积了20个令牌,则可瞬间处理20个请求,随后回归稳定速率。
五、实践建议与总结
- 明确目标:限流是防刷、防雪崩还是保核心?阈值需通过压测结合业务指标(如CPU、RT)来科学设定。
- 避免过度:过严的限流影响用户体验,过松则失去保护意义。需找到平衡点。
- 友好降级:被限流的请求应返回明确的提示信息(如HTTP 429),而非5xx错误。
- 持续监控:记录并分析限流触发日志,用于评估阈值合理性和发现异常流量模式。
限流是构建韧性系统的必备技能,它让服务在流量风暴中保有“呼吸”的空间,是通往高可用架构的关键一步。