高并发场景下为什么必须限流
后端服务在高并发场景下面临的核心矛盾是:请求速率超出系统处理能力时,不加限制地全量接纳会导致级联故障——线程池耗尽、数据库连接池打满、内存溢出,最终服务雪崩。限流是保护系统的第一道防线,其本质是在”拒绝部分请求”与”全盘崩溃”之间做取舍。微服务架构中,API网关层限流保护入口,服务间限流防止下游被上游压垮,这两层缺一不可。
四种限流算法的原理与实现
1. 固定窗口计数器
最简单的限流方案:将时间划分为固定窗口,每个窗口内维护一个计数器。实现简单,但存在窗口边界处突发流量翻倍的问题。
// 固定窗口计数器(Java实现)
public class FixedWindowRateLimiter {
private final long windowSizeMs;
private final int maxRequests;
private long windowStart;
private int counter;
public FixedWindowRateLimiter(long windowSizeMs, int maxRequests) {
this.windowSizeMs = windowSizeMs;
this.maxRequests = maxRequests;
this.windowStart = System.currentTimeMillis();
this.counter = 0;
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - windowStart >= windowSizeMs) {
windowStart = now;
counter = 0;
}
if (counter < maxRequests) {
counter++;
return true;
}
return false;
}
}
2. 滑动窗口日志
记录每个请求的时间戳,统计滑动窗口内的请求数。精确但内存开销大,适用于低QPS场景。
3. 令牌桶算法(Token Bucket)
生产环境最常用的限流算法。以固定速率向桶中放入令牌,请求消耗令牌,桶满则丢弃多余令牌,桶空则拒绝请求。允许瞬时突发流量(桶内有累积令牌),同时保证长期平均速率不超过阈值。
// 令牌桶算法(Go实现)
type TokenBucket struct {
rate float64 // 每秒放入令牌数
capacity float64 // 桶容量
tokens float64 // 当前令牌数
lastRefill time.Time // 上次填充时间
mu sync.Mutex
}
func NewTokenBucket(rate, capacity float64) *TokenBucket {
return &TokenBucket{
rate: rate,
capacity: capacity,
tokens: capacity, // 初始满桶
lastRefill: time.Now(),
}
}
func (tb *TokenBucket) TryAcquire(n float64) bool {
tb.mu.Lock()
defer tb.mu.Unlock()
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
tb.tokens = math.Min(tb.capacity, tb.tokens + elapsed*tb.rate)
tb.lastRefill = now
if tb.tokens >= n {
tb.tokens -= n
return true
}
return false
}
4. 漏桶算法(Leaky Bucket)
请求进入桶中排队,以固定速率流出处理。平滑流量但无法应对突发,适合对处理速率有严格要求的场景(如消息中间件消费端)。
分布式限流方案设计
单机限流无法保护共享资源(数据库、缓存),分布式限流需借助Redis实现全局计数:
// Redis + Lua脚本实现滑动窗口限流
-- rate_limiter.lua
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. ':' .. math.random(1e6))
redis.call('PEXPIRE', key, window)
return 1
else
return 0
end
// Spring Boot集成
@Component
public class RedisRateLimiter {
private final String SCRIPT = """
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. ':' .. math.random(1e6))
redis.call('PEXPIRE', key, window)
return 1
else
return 0
end
""";
@Autowired
private StringRedisTemplate redisTemplate;
public boolean tryAcquire(String resource, long windowMs, int limit) {
DefaultRedisScript script = new DefaultRedisScript<>(SCRIPT, Long.class);
Long result = redisTemplate.execute(script,
List.of("rate_limit:" + resource),
String.valueOf(windowMs),
String.valueOf(limit),
String.valueOf(System.currentTimeMillis())
);
return result != null && result == 1L;
}
}
降级策略设计:服务治理的兜底机制
限流拒绝请求后,降级策略决定用户体验。降级不是简单返回错误,而是提供有价值的替代响应:
读服务降级
- 返回缓存数据(即使过期),标注数据时效性
- 返回精简字段,省略计算密集型的聚合数据
- 降级到搜索引擎的预计算结果
写服务降级
- 写入消息队列异步处理,返回"已接受待处理"
- 写本地存储,异步同步到远端(最终一致性)
// Spring Boot降级注解
@CircuitBreaker(name = "orderService", fallbackMethod = "createOrderFallback")
public OrderResult createOrder(OrderRequest request) {
return orderClient.create(request);
}
public OrderResult createOrderFallback(OrderRequest request, Exception e) {
// 写入本地队列,异步补偿
localQueue.offer(request);
return OrderResult.accepted("订单已提交,预计30秒内确认");
}
熔断器模式:快速失败的防御机制
Resilience4j熔断器状态机:
// 熔断器配置
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率50%触发熔断
.slowCallRateThreshold(80) // 慢调用率80%触发熔断
.slowCallDurationThreshold(Duration.ofSeconds(3))
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断30秒
.permittedNumberOfCallsInHalfOpenState(10) // 半开状态放行10个请求
.slidingWindowType(SlidingWindowType.COUNT_BASED)
.slidingWindowSize(100) // 滑动窗口100个请求
.build();
// 熔断器事件监听
circuitBreaker.getEventPublisher()
.onStateTransition(event -> log.warn("CircuitBreaker {} state: {} -> {}",
event.getCircuitBreakerName(),
event.getStateTransition().getFromState(),
event.getStateTransition().getToState()))
.onError(event -> metrics.increment("circuit_breaker_error"))
.onSuccess(event -> metrics.increment("circuit_breaker_success"));
熔断器状态转换逻辑:Closed(正常放行)→ Open(全部拒绝)→ Half-Open(放行少量请求探测)→ 根据探测结果回到Closed或Open。这个状态机保护下游不被持续冲击,同时给下游恢复时间窗口。
限流降级监控告警体系
限流降级效果需要全链路可观测:
- 限流拒绝率:
rate(http_server_requests_seconds_count{status="429"}[5m]) / rate(http_server_requests_seconds_count[5m]) - 降级触发次数:统计fallback方法调用频率
- 熔断器状态变更:监控Open状态出现频次与持续时间
- 消息队列积压:异步降级场景下队列深度是关键指标
告警阈值建议:限流拒绝率 > 5%告警warning,> 15%告警critical;熔断器进入Open状态立即告警。限流是系统保护手段,但持续高拒绝率意味着系统容量不足,需要扩容而非仅依赖限流。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/gao-bing-fa-xi-tong-xian-liu-jiang-ji-fang-an-she-ji-shi/