在互联网高并发场景中,各种突发流量和攻击请求可能导致后端服务不堪重负。限流算法作为保护核心服务稳定性的重要手段,收到广泛应用。常见的限流方案包括漏桶算法、令牌桶算法、平滑限速和分布式令牌桶。本文将基于实际生产环境需求,采用方案对比分析型结构,深入对比各类限流算法的原理、优缺点,并结合 Java+Redis 等典型实现示例,给出选型建议与优化实践。
在高并发系统中,当请求速率超过服务最大承载能力时,将导致队列积压、响应超时甚至服务宕机。常见场景包括:
为了保障核心业务的可用性,需要在网关或者业务入口处限速、限流,通过有节奏地放行请求,平滑控制系统压力。
原理:将请求按固定速率从缓冲队列中取出处理,类似于水从漏斗中流出。 实现特点:
原理:以固定速率往桶中添加令牌,请求到来时尝试获取令牌,获取成功才放行。 实现特点:
如 Guava RateLimiter 的平滑突发模式,通过内部令牌桶算法实现的限速,兼顾平衡性与突发能力。
基于 Redis 或 ZooKeeper 等实现分布式令牌同步,适合多实例部署环境。常见实现:
方案 | 优点 | 缺点 ---------------|-----------------------------------------------|------------------------------ 漏桶算法 | 平滑稳定、算法简单 | 突发流量无法快速放行,队列易阻塞 令牌桶算法 | 支持突发、平滑与突发兼顾 | 实现相对复杂 平滑限速 | 简单易用(如 Guava RateLimiter) | 单机模式,突发控制有限 分布式令牌桶 | 跨实例统一限流,适合分布式部署 | 额外的 Redis 延迟与单点风险
import com.google.common.util.concurrent.RateLimiter;
public class GuavaLimiterExample {
// 每秒放行 100 个请求
private static final RateLimiter limiter = RateLimiter.create(100.0);
public void handleRequest() {
// 最大等待 500ms
boolean acquired = limiter.tryAcquire(1, 500, TimeUnit.MILLISECONDS);
if (!acquired) {
// 限流处理
System.out.println("请求被限流");
return;
}
// 正常处理逻辑
process();
}
private void process() {
// 业务逻辑
}
}
-- token_bucket.lua
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- 每秒生成令牌数
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3]) -- 当前时间戳(纳秒)
-- 获取当前桶状态
local last_time, tokens = unpack(redis.call('HMGET', key, 'last_time', 'tokens'))
if not last_time then
last_time = now
tokens = capacity
else
last_time = tonumber(last_time)
tokens = tonumber(tokens)
end
-- 计算新增令牌
local delta = math.max(0, now - last_time) / 1e9 * rate
tokens = math.min(capacity, tokens + delta)
last_time = now
if tokens < 1 then
-- 不足则拒绝
redis.call('HMSET', key, 'last_time', last_time, 'tokens', tokens)
return 0
else
-- 消耗令牌
tokens = tokens - 1
redis.call('HMSET', key, 'last_time', last_time, 'tokens', tokens)
return 1
end
Java 调用示例:
public boolean tryAcquire(String key) {
DefaultRedisScript script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("token_bucket.lua")));
script.setResultType(Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(key),
"100", "50", String.valueOf(System.nanoTime()));
return result != null && result == 1;
}
http {
limit_req_zone $binary_remote_addr zone=perip:10m rate=50r/s;
server {
location /api/ {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://backend;
}
}
}
本文对漏桶、令牌桶、平滑限速与分布式令牌桶四种限流算法进行了对比分析,并给出了不同场景下的选型建议。同时通过 Java+Guava、Redis Lua 脚本与 Nginx 限流配置示例,展示了实际应用中的优化实践。对于高并发系统,可根据业务特点灵活组合多层限流机制,以确保系统的稳定性和可扩展性。