C#中的速率限制与节流:驯服请求流

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C#中的速率限制与节流:如何保护API免受过载并保住用户的耐心

\n\n

想象一下:你发布了一个很棒的API。服务器运行飞快,数据库响应瞬间完成。但突然——砰!——一个不守规矩的客户端(或者更糟,DDoS攻击)开始以每秒数千个请求猛攻你的入口点。服务器崩溃,数据库窒息,而真正的用户只能看到503错误。熟悉吗?

\n\n

这个问题的解决方案是——速率限制和节流。在C#中,我们拥有所有工具来优雅且高效地实现这些机制。在本文中,我们不仅会讲解理论——我们还会编写真实代码,剖析流行算法,并找出针对特定任务的最佳选择。系好安全带,精彩即将开始!

\n\n

速率限制 vs 节流:有什么区别?

\n\n

尽管这两个术语经常被混淆,但它们之间有一条清晰的界限:

\n
    \n
  • 速率限制——是一种“硬性”封锁。如果用户超过了限制(例如,每分钟100个请求),服务器会返回HTTP 429(请求过多)。请求根本不会到达业务逻辑层。
  • \n
  • 节流——是一种“软性”减速。服务器接受请求,但会延迟处理或降低优先级。用户不会收到错误,但他们的操作速度会下降。
  • \n
\n

在C#中,我们可以实现这两种方法,通常针对不同场景将它们结合起来使用。

\n\n

速率限制算法:从简单到复杂

\n\n

在编写代码之前,让我们先了解其数学原理。有四种“黄金”算法构成了90%解决方案的基础。

\n\n

1. 固定窗口

\n

最简单的方法。我们将时间划分为固定的间隔(例如,1分钟),并统计每个窗口内的请求数。一旦计数器达到限制——就进行封锁。

\n

优点:实现简单。缺点:“边界效应”问题(窗口切换时的突发流量)。

\n\n
public class FixedWindowRateLimiter\n{\n    private readonly int _limit;\n    private readonly TimeSpan _windowSize;\n    private int _counter;\n    private DateTime _windowStart;\n\n    public FixedWindowRateLimiter(int limit, TimeSpan windowSize)\n    {\n        _limit = limit;\n        _windowSize = windowSize;\n        _windowStart = DateTime.UtcNow;\n    }\n\n    public bool AllowRequest()\n    {\n        var now = DateTime.UtcNow;\n        if (now - _windowStart >= _windowSize)\n        {\n            // 重置窗口\n            _counter = 0;\n            _windowStart = now;\n        }\n\n        if (_counter >= _limit)\n            return false; // 请求过多\n\n        Interlocked.Increment(ref _counter);\n        return true;\n    }\n}
\n\n

2. 滑动窗口

\n

固定窗口的演进。我们不再使用硬性边界,而是在一个滑动的时间段内统计请求。通常通过记录时间戳(例如,在环形缓冲区中)来实现。

\n

优点:平滑突发流量。缺点:实现稍复杂,需要内存来存储时间戳。

\n\n
public class SlidingWindowRateLimiter\n{\n    private readonly int _limit;\n    private readonly TimeSpan _windowSize;\n    private readonly Queue<DateTime> _timestamps = new();\n\n    public SlidingWindowRateLimiter(int limit, TimeSpan windowSize)\n    {\n        _limit = limit;\n        _windowSize = windowSize;\n    }\n\n    public bool AllowRequest()\n    {\n        var now = DateTime.UtcNow;\n        lock (_timestamps)\n        {\n            // 移除过期的时间戳\n            while (_timestamps.Count > 0 && now - _timestamps.Peek() > _windowSize)\n                _timestamps.Dequeue();\n\n            if (_timestamps.Count >= _limit)\n                return false;\n\n            _timestamps.Enqueue(now);\n            return true                

博客

书籍推荐