为什么速率限制对Node.js SaaS至关重要
SaaS API通常需要同时满足多种限制需求:
- 滥用防护:阻止机器人、爬虫、暴力登录尝试和异常客户端。
- 基础设施保护:避免Node.js工作进程、数据库、队列和第三方API过载。
- 产品配额:落实免费、专业、团队及企业套餐的边界。
- 成本控制:防止昂贵的后台任务、AI调用、邮件发送或Webhook重试失控。
- 公平性控制:确保单一租户不会耗尽其他租户所需的共享容量。
一个简单的公共端点可能只需要基于IP的限制。而一个严肃的SaaS API通常需要按用户、按团队、按令牌、按路由、按套餐的限流。这正是工具选择开始发挥关键作用的地方。
执行速率限制的四个层面
主要有四种常见的执行层,各有利弊,涉及成本、上下文和运维复杂度。
1. 应用中间件
这是最直接的方式。在Express、Fastify、NestJS或其他Node.js框架中安装中间件,并在请求到达处理器之前进行评估。
应用中间件适用于:
- 登录和注册保护。
- 密码重置保护。
- 用户感知的限制。
- SaaS套餐感知的配额。
- 路由级业务规则。
缺点在于,一旦运行多个Node.js进程、容器或地域,基础的内存中间件就不可靠。express-rate-limit 的文档明确指出,默认内存存储将命中计数保存在内存中,当使用多个服务器或进程时会产生不一致的结果。对于多实例API,推荐使用外部存储。
2. 基于Redis的分布式速率限制
Redis是分布式速率限制的常用状态存储,因为它速度快、可共享并支持原子操作。基于Redis的限制器可以在多个Node.js实例、容器或工作进程之间保持配额状态一致。
这种方式适用于:
- 多实例Node.js API。
- SaaS套餐配额。
- API密钥限制。
- 暴力登录防护。
- Webhook和第三方API成本保护。
- 同一用户可能访问不同应用实例的分布式部署。
类似 Upstash Rate Limit 和 rate-limiter-flexible 这样的工具简化了这一模式。Upstash Rate Limit 专为无服务器、基于HTTP的无连接环境设计,如 Serverless 函数、Vercel Edge、Cloudflare Workers、Fastly Compute@Edge 等更倾向HTTP而非TCP的场景。Upstash还提供了一些实用功能,如缓存被阻止的请求、超时行为、分析、流量保护、自定义速率、多区域使用、多重限制和动态限制。
3. 边缘与WAF速率限制
边缘速率限制在流量到达源站之前生效。Cloudflare WAF速率限制规则允许团队定义匹配表达式、请求阈值、缓解持续时间以及达到设定速率后的操作。
边缘限制适用于:
- 在到达Node.js之前拦截明显的滥用。
- 保护登录、搜索和公共API端点。
- 基于国家、IP、用户代理、路径、方法和标头的过滤。
- 在流量尖峰时减少源站负载。
其代价在于,边缘限制可能无法理解全部应用状态。它们擅长粗粒度防护,但对于套餐感知的SaaS配额往往不够精细。
4. API网关速率限制
API网关在应用之外、更靠近API管理的位置执行限制。Kong Gateway的速率限制插件可以按服务或路由施加限制,发送标准限流标头,并使用Redis进行分布式部署。像Zuplo这样的托管API网关产品则将速率限制与API密钥、开发者门户、变现、路由、缓存和API治理相结合。当速率限制是更广泛平台需求的一部分,而非单一中间件特性时,这种方式更具意义。
对比表格
| 选项 | 最适合 | 优势 | 注意事项 | 适用场景 |
|---|---|---|---|---|
express-rate-limit | 早期Express应用、登录端点、简单滥用防护 | 安装简便、框架原生、成本低 | 内存存储不足以支持多实例;产品配额功能有限 | MVP与单实例API |
rate-limiter-flexible + Redis | 分布式Node.js API | Redis状态存储、灵活模式、提供登录保护示例 | 需要自行运维Redis与限制逻辑 | 多实例的成长型SaaS API |
| Upstash Rate Limit | 无服务器与边缘主导的Node.js应用 | HTTP驱动、无服务器友好、分析、多区域支持、动态限制 | 成本取决于命令数量、流量和Redis用量 | Vercel、Cloudflare Workers、边缘与无服务器应用 |
| Arcjet | 应用级安全与配额控制 | 代码级限制、机器人防护、AI预算控制、无需Redis维护限流状态 | 依赖Arcjet平台;请确认当前定价 | 希望在代码内集成安全控制的SaaS应用 |
| Cloudflare WAF速率限制 | 边缘滥用控制 | 在源站前阻断流量,支持仪表板/API/Terraform工作流,WAF集成 | 应用感知度较低;高级特性取决于套餐 | 受Cloudflare保护的公共API与登录端点 |
| Kong Rate Limiting Plugin | API网关团队 | 按服务/路由限制、标准标头、分布式网关Redis策略 | 网关运维与配置复杂度 | 已在使用Kong的平台团队 |
| Zuplo | 托管API平台与开发者门户 | API密钥、变现、开发者门户、速率限制、GitOps、边缘网关模型 | 定价与套餐适配需确认 | API产品、开发者平台、对外的SaaS API |
方案一:Express中间件
对于一个小型Express API,express-rate-limit 是快速实现限流的最短路径。
import express from "express";
import rateLimit from "express-rate-limit";
const app = express();
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 20,
standardHeaders: "draft-8",
legacyHeaders: false,
});
app.post("/login", loginLimiter, async (req, res) => {
res.json({ ok: true });
});
这对早期阶段的滥用防护很有用,但请将其视为起点。对于生产环境的SaaS,关键问题是存储是否在所有实例间共享。若不共享,同一客户端可能会因为每个进程拥有独立的计数器而被允许超过预期的请求量。
当逻辑需要用户上下文时,应使用应用中间件,例如:
- 免费套餐:每分钟100次请求。
- 专业套餐:每分钟1000次请求。
- 企业套餐:自定义配额。
- 登录端点:更严格的每IP和每账户限制。
- AI端点:基于令牌预算的限制。
方案二:基于Redis的分布式速率限制
对许多Node.js SaaS应用而言,基于Redis的限制是实用的中间地带。它将状态从单进程中移出,从而可以在水平扩展的同时保持配额一致性。
基于Redis的限制器可以追踪类似这样的键:
rate:user:123:/api/search
rate:team:acme:/api/export
rate:ip:203.0.113.10:/login
rate:api_key:sk_live_xxx:/v1/events
对于SaaS产品,键的设计比包名更重要。糟糕的键会导致不公平的限制。优秀的键应与你的产品模型相匹配。
常用标识符包括:
- IP地址:用于匿名滥用防护。
- 用户ID:用于登录后的产品。
- 团队ID:用于B2B SaaS。
- API密钥ID:用于开发者API。
- 路由组:用于高开销端点。
- 套餐等级:用于商业配额。
下面使用 rate-limiter-flexible 和Redis展示一个实际示例:
import { RateLimiterRedis } from "rate-limiter-flexible";
import Redis from "ioredis";
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
enableOfflineQueue: false,
});
const limiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: "rl",
points: 100,
duration: 60,
});
async function rateLimitMiddleware(req, res, next) {
const key = `user:${req.userId}:${req.route}`;
try {
const result = await limiter.consume(key);
res.set("Retry-After", String(Math.ceil(result.msBeforeNext / 1000)));
res.set("X-RateLimit-Remaining", String(result.remainingPoints));
next();
} catch (err) {
if (err.remainingPoints !== undefined) {
const retryAfter = Math.ceil(err.msBeforeNext / 1000);
res.set("Retry-After", String(retryAfter));
res.status(429).json({
error: "Too Many Requests",
retryAfter,
});
} else {
next(err);
}
}
}
Redis托管:Upstash与传统Redis
当希望获得通过HTTP访问的无服务器友好型Redis,并且不想管理持久TCP连接时,Upstash很有吸引力。而当Node.js应用已运行在持久化服务器环境中,并能高效维护TCP连接时,传统Redis提供商则更具优势。
| 因素 | Upstash | 自管理Redis |
|---|---|---|
| 连接模型 | HTTP驱动 | 持久TCP |
| 无服务器适配 | 极佳 | 需连接池 |
| 定价模型 | 按命令或固定套餐 | 基于内存与实例 |
| 多区域 | 内置 | 需手动复制 |
| 分析仪表板 | 包含 | 需外部工具 |
| 免费层 | 256 MB,每月50万条命令 | 因提供商而异 |
方案三:Arcjet用于应用级安全限制
当速率限制是更广泛的运行时安全层的一部分时,Arcjet非常有用。其文档描述了在代码内部配置的应用级限制,使用场景包括AI令牌开销控制、登录保护、API节流以及SaaS套餐配额。Arcjet通过其云API处理速率限制的状态跟踪,因此你无需为限流状态单独维护Redis基础设施。
这使得Arcjet在以下场景中成为有力选项:
- 在应用代码中实现按路由和按用户的限制。
- 在速率限制旁边提供机器人防护。
- AI预算控制。
- 随产品逻辑变化的SaaS配额规则。
- 减少Redis基础设施的运维负担。
其代价是平台依赖性。如果你已有Redis并希望完全控制,基于Redis的限制器可能更具可移植性。如果你希望随代码提供安全控制,Arcjet能够减少运维工作。
import arcjet, { rateLimit, shield } from "@arcjet/node";
const aj = arcjet({
key: process.env.ARCJET_KEY,
rules: [
rateLimit({
type: "tokenBucket",
refillRate: 10,
interval: "10s",
capacity: 100,
}),
shield({ mode: "LIVE" }),
],
});
app.post("/api/ai", async (req, res) => {
const decision = await aj.protect(req, { requested: 5 });
if (decision.isDenied()) {
return res.status(429).json({ error: "Rate limit reached" });
}
res.json({ result: "ok" });
});
方案四:Cloudflare WAF速率限制
Cloudflare WAF速率限制是理想的第一道防线。它能够在流量到达Node.js源站之前进行拦截、质询或以其他方式缓解。
以下端点适合使用Cloudflare速率限制:
/login/signup/api/search/api/export- 未经认证的公共端点。
- 接收机器人流量的端点。
- 需在到达源站前过滤流量的端点。
然而,Cloudflare也指出,因为检测和计数器时机的原因,速率限制规则并不能保证到达源站的请求数量精确无误。这对粗粒度的滥用防护来说不是问题,但对精确的计费配额而言却很重要。为了实现精确的SaaS配额,应将边缘限制与应用层的用户或API密钥限制结合使用。
边缘限制通常通过Cloudflare仪表板、API或Terraform配置。一条典型规则可能如下:
- 字段: URI Path
- 运算符: equals
- 值:
/api/search - 表达式:
(http.request.uri.path eq "/api/search") - 速率: 当同一IP每分钟请求超过30次时,阻断10分钟。
方案五:Kong用于API网关速率限制
当你的公司已在使用API网关层时,Kong是一个强大的选择。其速率限制插件可以输出 RateLimit-Limit、RateLimit-Remaining、RateLimit-Reset 和 Retry-After 等标头,并在超出限制时返回429状态码。
Kong特别适用于以下情况:
- API已通过Kong路由。
- 多个团队在同一网关后发布服务。
- 需要按服务或按路由的治理。
- 希望在Node.js代码库之外统一API限制。
- 需要跨网关节点的Redis状态一致性。
对于单个Node.js SaaS,Kong可能过于重型。但对于管理多个API的平台团队,它可以集中执行策略。
# Kong 声明式配置示例 (kong.yml)
services:
- name: search-api
url: http://search-service:3000
routes:
- name: search-route
paths:
- /api/search
plugins:
- name: rate-limiting
config:
minute: 30
policy: redis
redis_host: redis.default.svc.cluster.local
redis_port: 6379
fault_tolerant: true
hide_client_headers: false
方案六:Zuplo用于托管API产品
Zuplo更接近一个托管API网关与开发者平台,而非单纯的速率限制库。其定价页面重点展示API网关能力,如认证、路由、治理、开发者门户、变现、AI网关、缓存和成本追踪。
如果你的Node.js SaaS向开发者公开API,并且需要以下特性,Zuplo会很有用:
- API密钥。
- 开发者文档。
- 用量计量。
- 请求路由。
- 变现。
- 网关级策略。
- GitOps工作流。
如果只需要登录端点保护,Zuplo可能过重。但如果API本身是一个产品,它能够替代多个自研工具。
成本与运营权衡
速率限制工具的成本模型差异很大。在选择前了解这些差异,可以避免后续重构。
中间件成本
基础中间件几乎免费,但当需要分布式一致性时,真正成本才会显现。这时需要Redis、数据库存储或托管服务。
Redis成本
基于Redis的限制通常按内存、命令数、带宽和高可用性需求计费。Upstash列出了按请求计费的随用随付方案,以及面向稳定用量的固定套餐。其免费层包含256 MB数据大小和每月50万条命令,随用随付则按每10万条命令定价。云服务定价会变动,请以官方最新价格为准。
边缘WAF成本
边缘WAF限制通常与CDN或安全平台套餐绑定。优势是源站保护,局限是应用上下文较弱。截至2026年,Cloudflare速率限制规则在Pro及以上套餐中提供,请核实当前计划详情。
API网关成本
网关工具可能按请求数、席位、工作区、支持等级、自定义域名、分析或企业安全功能收费。Zuplo公共页面列出了每月10万次请求的免费层及包含请求量的Builder计划,正式发布前请确认生产环境定价。
费用对比总览
| 工具 | 免费层 | 入门级付费 | 定价模型 | 主要成本驱动 |
|---|---|---|---|---|
| express-rate-limit | 免费 (MIT) | N/A | 开源 | 自行基础设施 |
| rate-limiter-flexible + Redis | 免费 (ISC) | Redis约$5-50/月 | 开源+Redis托管 | Redis实例规格 |
| Upstash Rate Limit | 256 MB,50万条命令 | 每10万条命令 | 基于用量 | 命令量 |
| Arcjet | 提供免费层 | 联系销售 | 平台定价 | 请求量、功能 |
| Cloudflare WAF | 免费计划不支持 | Pro套餐($20/月起) | 套餐制 | CDN/WAF套餐等级 |
| Kong | 免费 (OSS) | 企业定价 | 开源或订阅 | 基础设施+运维 |
| Zuplo | 10万次请求/月 | Builder计划 | 按请求 | API调用量 |
按SaaS阶段推荐架构
MVP或个人项目
对公共端点和登录滥用防护使用 express-rate-limit。保持实现简单。添加清晰的429响应和 Retry-After 标头。在此阶段不要过度设计。
早期生产级SaaS
为API密钥、用户账户和高开销端点添加Redis支持的限制。保持边缘防护启用以应对明显的机器人和暴力攻击流量。开始记录被阻止请求的指标。
成长型B2B SaaS
采用分层模型:
- Cloudflare或其他WAF:进行粗粒度边缘滥用防护。
- 应用级限制:用于用户、团队和套餐感知的配额。
- Redis或托管型限制器:满足分布式状态共享。
- 监控与告警:针对被阻止请求、接近限额的租户以及异常流量尖峰。
API产品或开发者平台
当速率限制是更广泛的API管理故事(如API密钥、开发者门户、用量追踪、变现、路由、转换和治理)的一部分时,考虑使用Kong或Zuplo等API网关。
生产环境检查清单
在Node.js SaaS应用中上线速率限制之前,请确认以下事项:
- 按端点类别而非全局定义限制。
- 对匿名用户、认证用户、API密钥和团队使用不同的限制。
- 保持限制与SaaS定价层级一致。
- 当运行多个实例时,使用Redis或托管服务。
- 统一返回
429 Too Many Requests。 - 尽可能包含
Retry-After标头。 - 不要在拒绝响应中泄露敏感账户信息。
- 记录被阻止的请求,包含路由、租户、用户、API密钥和原因。
- 对被阻止请求的突然激增设置告警。
- 通过独立规则放行可信的内部任务与Webhook。
- 避免在边缘意外阻止经过验证的搜索引擎爬虫。
- 在API文档中说明限流行为。
实用建议
对于大多数Node.js SaaS团队,最佳起步架构是分层的:
- Cloudflare WAF:用于边缘级滥用控制。
- 应用中间件:用于端点特定逻辑。
- Redis支持或托管型限制器:用于分布式用户与套餐配额。
- API网关:仅当API管理成为产品或平台需求时采用。
不要将速率限制视为单一的库选择。应将其视为SaaS控制平面的一部分。正确的选择取决于你需要上下文的场景、配额的精确程度、运行的实例数量,以及API治理是否属于产品范畴。
结语
速率限制不仅仅是安全特性。对于Node.js SaaS产品,它同时也是成本控制层、产品封装层和运维安全层。
早期采用中间件快速起步,需要分布式一致性时引入Redis,需要边缘防护时使用Cloudflare,已运营API网关时选择Kong,而当你的公共API需要网关、门户、变现和开发者平台功能时,Zuplo则值得考虑。
长期最佳方案通常并非单一工具,而是一种分层架构,将粗粒度滥用防护与精确的产品配额执行解耦。从简单开始,监控被阻止的请求,并随着SaaS的成长演进你的速率限制策略。