SaaS平台一旦把API开放给外部用户,接口就不再只是应用内部的一组函数调用,而变成了一个需要独立治理的公共资源入口。正常用户可能每秒发起几次请求,某个脚本却可能在极短时间内发送数千甚至数万次请求;一个配置错误的客户端也可能不断重试,把原本正常的业务流量放大成数据库、Redis、第三方支付接口甚至AI服务的压力。
因此,API限流的核心目的不是简单地“挡住攻击者”,而是在系统资源有限的情况下决定谁可以在什么时间消耗多少资源。它保护的是API本身,也保护API背后的数据库、缓存、消息队列、第三方服务和计算资源。
限流与DDoS防护也不能混为一谈。应用层限流可以减少大量请求进入业务逻辑,但如果攻击流量已经达到网络层面的巨大规模,应用服务器可能在执行限流逻辑之前就已经承受连接、带宽或TLS握手压力。这类问题通常需要CDN、WAF、云厂商DDoS防护以及负载均衡等更靠前的基础设施处理。Cloudflare目前也将Rate Limiting作为WAF体系的一部分,用于限制特定URL或域名上的过量请求。
API限流最常见的算法包括固定窗口、滑动窗口、令牌桶和漏桶。固定窗口最容易实现,例如规定某个API每分钟最多允许100次调用。系统按照时间窗口建立计数器,超过100次之后返回HTTP 429。它的实现成本低,但窗口边界可能出现突发问题。假设一个客户端在第一分钟最后一秒发送100次,下一分钟第一秒又发送100次,那么两秒内可能出现200次请求。
滑动窗口试图减少这种边界效应。系统不再只看一个固定时间段,而是计算最近一段时间内的请求数量。实现方式可以使用Redis的有序集合、时间戳记录或者其他专门的数据结构,但随着请求规模增加,存储和计算成本也会增加。
令牌桶则更适合需要允许一定突发流量的API。系统按照固定速率向桶中补充令牌,每次请求消耗一个或多个令牌。当桶里还有令牌时,请求可以立即执行;没有令牌时则被拒绝或者等待。这样既可以控制长期平均速率,又可以允许一定程度的瞬时峰值。
漏桶的思路不同。它更强调以相对稳定的速率处理请求。Nginx的ngx_http_limit_req_module就是一个典型实现,官方文档明确说明该模块采用leaky bucket,也就是漏桶模型,并可以通过burst控制允许积累的突发请求数量。
对于SaaS平台来说,算法只是第一层问题,更重要的是确定“按照什么东西限流”。单纯按照IP地址限流看起来简单,但在真实互联网环境中并不可靠。公司网络、校园网络、移动运营商甚至NAT环境下,很多正常用户可能共享一个公网IP;反过来,攻击者也可以通过代理、僵尸网络或者大量出口IP绕开单IP限制。
因此,身份认证之后的用户、API Key、租户以及具体接口通常更适合作为重要限流维度。例如普通免费用户可以设置每分钟100次调用,专业套餐可以设置每分钟1000次;某个租户可以拥有独立的每秒请求额度;一个昂贵的AI推理接口又可以单独设置更低的并发限制。
这也是SaaS限流与普通网站限流的一个重要区别。SaaS平台需要考虑租户隔离。假设一个大型租户和十个小型租户共享同一个API服务,如果只按照服务器整体流量设置一个阈值,那么一个租户的大量调用就可能挤占其他租户的资源。更合理的设计通常是同时存在全局限制、租户限制、用户限制和接口限制。
例如,一个请求进入系统之后,可以依次经过边缘防护、API网关、认证、租户识别和业务服务。网关负责快速拦截明显异常的请求,API服务再根据用户、API Key和租户执行更精细的配额检查。这样做比把所有限流逻辑全部塞进业务Controller更加容易维护。
分布式SaaS系统还会遇到一个非常实际的问题:多个应用实例如何共享限流状态。
假设系统有10台API服务器,如果每台服务器都在本地内存里维护“每分钟100次”的计数器,那么同一个用户只要请求被负载均衡到不同实例,就可能获得远高于100次的实际额度。Redis因此经常被用于集中保存限流状态。Redis官方文档也明确把每用户、API、租户等维度的分布式请求配额作为典型限流场景。
但这里有一个经常被写错的地方:使用Redis并不意味着必须给每一次INCR加分布式锁。Redis的INCR本身就是原子操作,适合实现简单的计数器。对于INCR和过期时间需要一起完成的复杂场景,可以使用事务、Lua脚本等机制保证操作的一致性;较新的Redis版本甚至提供了INCREX,可以在一次命令中完成递增和过期控制。
真正需要谨慎处理的是限流状态的生命周期。假如一个固定窗口计数器设置了60秒过期时间,那么每次请求都错误地刷新TTL,就可能导致这个窗口永远不结束。Redis官方的限流示例也专门讨论了计数器和过期时间之间的原子性以及竞态条件。
除了请求数量,还应该考虑并发数。一个API每秒允许100次请求,并不意味着系统一定能够同时处理100个请求。如果其中每个请求都会执行一个5秒钟的数据库查询,那么瞬间积累的并发任务可能很快把连接池耗尽。
因此,在某些接口上,“每秒请求数”和“同时执行请求数”应该分别限制。例如上传文件、AI推理、PDF转换、视频转码和复杂报表查询,都可能属于单次成本较高的接口。这类接口除了RPS限制之外,还可以设置并发数、请求体大小、执行时间和单用户任务数等限制。
API配额也应该与业务成本联系起来。一个查询用户资料的接口和一个调用大型语言模型的接口,不应该使用同一个限流阈值。前者可能主要消耗数据库连接,后者则可能消耗GPU时间和第三方API额度。如果系统采用统一的“每分钟1000次”限制,可能保护了一个资源,却放任另一个资源被耗尽。
对于按调用次数收费的SaaS产品,还要把“限流”和“计费”分开设计。限流负责决定请求能不能进入系统,计费负责记录这次业务调用是否产生费用。两者虽然有关联,但不能简单地用一个Redis计数器同时充当限流系统和财务账本。计费数据通常需要更严格的持久化、幂等和审计机制。
HTTP层面的行为也应该统一。请求超过限制时,通常应该返回429 Too Many Requests,并尽可能通过Retry-After等响应信息告诉客户端何时可以再次尝试。成熟的API服务还可以提供当前配额、剩余额度和窗口重置时间等信息。Cloudflare自己的API文档目前就使用Ratelimit、Ratelimit-Policy和Retry-After等响应头表达限额和恢复时间。
客户端同样不能无限重试。一个非常常见的事故链是:服务器过载,开始返回429;客户端没有理解429含义,立即自动重试;服务器收到更多请求,再返回429;客户端继续重试。最终限流器没有解决问题,反而形成了重试风暴。
因此,API客户端应该使用指数退避、随机抖动和最大重试次数。对于幂等请求,可以安全地进行有限重试;对于支付、订单创建等非幂等操作,则必须配合幂等键,不能简单地因为网络超时就重新提交一次。
监控也是限流系统的一部分。不能只记录“请求被拒绝了多少次”,还应该观察不同租户的请求量、429比例、接口延迟、CPU、内存、数据库连接池、Redis延迟、队列长度以及下游服务错误率。如果某个API突然出现大量429,同时数据库连接数已经达到上限,那么问题可能不是限流阈值设置得太低,而是数据库已经成为瓶颈。
动态限流也不应该理解成“系统一发现流量增加,就自动把阈值调高”。这种做法可能恰恰把系统推向崩溃。动态策略应该建立在明确的容量模型和安全边界之上。例如系统健康时允许一定突发流量,一旦CPU、数据库连接池或下游错误率达到保护阈值,就降低并发额度或拒绝高成本请求。保护系统时,降低负载往往比继续接受请求更加重要。
工程实践中,限流最好形成多层结构。最外层可以由CDN、WAF和边缘安全系统过滤明显异常流量;API网关处理IP、路径、API Key等快速规则;应用层根据用户、租户和业务状态进行精细控制;具体服务再根据数据库、消息队列、第三方API等资源实施并发保护。这样即使某一层规则失效,后面的资源保护仍然存在。
限流也不是熔断。限流解决的是“进入多少请求”,熔断解决的是“下游已经异常时是否继续调用”。例如支付服务响应时间突然从200毫秒增加到10秒,API层即使没有超过请求数量限制,也可能因为大量请求持续占用连接而拖垮整个系统。这时需要超时、熔断、舱壁隔离、队列和降级策略共同参与。
对于SaaS开发者来说,限流设计最后应该落到一个非常具体的问题:一个请求究竟消耗了什么资源。是CPU、数据库连接、Redis操作、文件存储、网络带宽、GPU,还是第三方API额度?找到最容易被耗尽的资源,再决定限流维度和算法,通常比先选择一个现成的Token Bucket库更加重要。
API开放之后,安全边界就从“用户能不能登录”扩展到了“用户可以消耗多少资源”。认证解决身份问题,授权解决能做什么,租户隔离解决能访问哪些数据,限流解决单位时间内能消耗多少资源,熔断和超时解决下游故障时如何避免连锁崩溃,而WAF和DDoS防护则承担更靠前的流量安全职责。把这些机制分开设计,再让它们在同一条请求链路上协同工作,SaaS API才不会因为一次流量异常就把整个业务拖垮。
API 接口开放以后如何防止滥用,限流机制是 SaaS 开发的重要环节
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP