在分布式系统和高并发应用中,限流(Rate Limiting)是一项关键技术,用于防止过多的请求淹没服务器,从而保证服务的稳定性与性能。本文介绍几种常见的限流算法,包括固定窗口限流、滑动窗口限流、令牌桶算法、漏桶算法。
固定窗口限流 {#%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E9%99%90%E6%B5%81}
原理 {#%E5%8E%9F%E7%90%86}
固定窗口限流在一个固定的时间窗口内(如1秒、1分钟),限制请求数量。例如,一个用户在1分钟内最多只能发送100个请求。如果请求数超过阈值,则拒绝请求。时间窗口结束后,计数器重置。
优点 {#%E4%BC%98%E7%82%B9}
-
实现简单:使用简单的计数器即可实现。
-
低资源占用:不需要大量额外的计算和存储资源。
缺点 {#%E7%BC%BA%E7%82%B9}
- 临界问题:在时间窗口的边界可能出现大量突发请求。例如,一个用户在窗口结束前发出多个请求,并在新窗口开始时再次发出多个请求,导致短时间内服务器受到过多请求的压力。
代码示例 {#%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B}
public class RateLimiterDemo
{
private readonly int _maxRequests;
private readonly TimeSpan _windowTime;
private int _requestCount;
private DateTime _windowStart;
public RateLimiterDemo(int maxRequests, TimeSpan windowTime)
{
_maxRequests = maxRequests;
_windowTime = windowTime;
_windowStart = DateTime.UtcNow;
_requestCount = 0;
}
public bool IsRequestAllowed()
{
var now = DateTime.UtcNow;
if (now - _windowStart > _windowTime)
{
_windowStart = now;
_requestCount = 0;
}
if (_requestCount < _maxRequests)
{
_requestCount++;
return true;
}
return false;
}
}
滑动窗口限流 {#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E9%99%90%E6%B5%81}
原理 {#%E5%8E%9F%E7%90%86-1}
滑动窗口限流通过将时间窗口细分为多个小窗口,并对每个小窗口的请求进行统计来平滑流量。它在每个时间点都会计算过去固定时间内的请求总数,避免固定窗口的临界问题。
优点 {#%E4%BC%98%E7%82%B9-1}
-
解决临界问题:滑动窗口避免了固定窗口临界点的突发流量问题。
-
限流更加平滑:更加精准地控制请求频率。
缺点 {#%E7%BC%BA%E7%82%B9-1}
-
实现复杂:需要存储每个小窗口的请求计数,并动态更新窗口。
-
较高的资源开销:需要更多的计算和存储空间。
代码示例 {#%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B-1}
public class RateLimiterDemo
{
private readonly int _maxRequests;
private readonly TimeSpan _windowTime;
private readonly LinkedList<DateTime> _requestTimestamps = new LinkedList<DateTime>();
public RateLimiterDemo(int maxRequests, TimeSpan windowTime)
{
_maxRequests = maxRequests;
_windowTime = windowTime;
}
public bool IsRequestAllowed()
{
var now = DateTime.UtcNow;
var windowStart = now - _windowTime;
while (_requestTimestamps.Count &gt; 0 &amp;&amp; _requestTimestamps.First.Value &lt; windowStart)
{
_requestTimestamps.RemoveFirst();
}
if (_requestTimestamps.Count &lt; _maxRequests)
{
_requestTimestamps.AddLast(now);
return true;
}
return false;
}
}
令牌桶算法 {#%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95}
原理 {#%E5%8E%9F%E7%90%86-2}
令牌桶算法通过生成和消耗"令牌"来控制请求速率。系统以固定速率生成令牌并放入桶中,请求到达时从桶中取走一个令牌,如果桶中没有令牌,则拒绝请求。未使用的令牌可以累积,因此允许一定的突发流量。
优点 {#%E4%BC%98%E7%82%B9-2}
-
支持突发流量:未使用的令牌可以累积,从而允许短期的流量高峰。
-
灵活性高:可以通过调整令牌生成速率和桶的大小来调整限流策略。
缺点 {#%E7%BC%BA%E7%82%B9-2}
- 精确控制较弱:突发流量过大时仍可能导致系统压力增加。
代码示例 {#%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B-2}
public class RateLimiterDemo
{
private readonly int _capacity;
private readonly int _tokensPerSecond;
private int _tokens;
private DateTime _lastRefillTime;
public RateLimiterDemo(int capacity, int tokensPerSecond)
{
_capacity = capacity;
_tokensPerSecond = tokensPerSecond;
_tokens = capacity;
_lastRefillTime = DateTime.UtcNow;
}
public bool IsRequestAllowed()
{
var now = DateTime.UtcNow;
var timeElapsed = (now - _lastRefillTime).TotalSeconds;
// Refill tokens
var tokensToAdd = (int)(timeElapsed * _tokensPerSecond);
_tokens = Math.Min(_capacity, _tokens + tokensToAdd);
_lastRefillTime = now;
if (_tokens &gt; 0)
{
_tokens--;
return true;
}
return false;
}
}
漏桶算法 {#%E6%BC%8F%E6%A1%B6%E7%AE%97%E6%B3%95}
原理 {#%E5%8E%9F%E7%90%86-3}
漏桶算法将请求流量控制为一个固定速率。它模拟了一个装水的桶,水(请求)可以以任意速率流入桶中,但只能以固定速率流出(处理请求)。如果桶满了,超出的请求将被丢弃。该算法强制将请求处理速率保持稳定。
优点 {#%E4%BC%98%E7%82%B9-3}
-
严格控制流量:漏桶算法能严格限制请求的处理速率,流量平滑。
-
适用于带宽控制:特别适用于需要严格限速的场景。
缺点 {#%E7%BC%BA%E7%82%B9-3}
- 不支持突发流量:多余的请求直接丢弃,无法处理突发流量。
代码示例 {#%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B-3}
public class RateLimiterDemo
{
private readonly int _capacity;
private readonly int _dripRate; // Requests per second
private int _currentLoad;
private DateTime _lastLeakTime;
public RateLimiterDemo(int capacity, int dripRate)
{
_capacity = capacity;
_dripRate = dripRate;
_currentLoad = 0;
_lastLeakTime = DateTime.UtcNow;
}
public bool IsRequestAllowed()
{
var now = DateTime.UtcNow;
var timeElapsed = (now - _lastLeakTime).TotalSeconds;
// Leak the bucket
var leaks = (int)(timeElapsed * _dripRate);
_currentLoad = Math.Max(0, _currentLoad - leaks);
_lastLeakTime = now;
if (_currentLoad &lt; _capacity)
{
_currentLoad++;
return true;
}
return false;
}
}
总结 {#%E6%80%BB%E7%BB%93}
| 算法 | 优点 | 缺点 | 适用场景 | |--------|--------------|------------------------|--------------------| | 固定窗口限流 | 实现简单,计算资源开销小 | 存在临界问题,无法很好地控制短期内的突发流量 | 适合简单的API限流场景 | | 滑动窗口限流 | 解决临界问题,限流平滑 | 实现复杂,存储和计算开销较大 | 高并发场景、需要精确控制的限流场景 | | 令牌桶算法 | 支持突发流量,灵活调整 | 短期突发请求过大时可能无法很好地控制流量 | 实时通信、支付系统、API限流等场景 | | 漏桶算法 | 流量控制严格,平稳处理 | 不支持突发流量,突发请求可能被丢弃 | 带宽控制、流量整形等严格限速的场景 |