好的,我们来探讨安全与性能领域一个至关重要的防御措施:速率限制 (Rate Limiting)。这就像是为你的应用配备一位高效的“交通警察”,他能确保在高流量时段,道路依然畅通,防止恶意拥堵和资源滥用。
为你的应用配备“交通警察”:NestJS 速率限制完全指南
想象你的应用是一家提供免费查询服务的公共信息亭。
这个信息亭(你的 API)非常受欢迎,但它的处理能力是有限的。这时,可能会出现几种情况:
- 普通用户:每隔几秒钟来查询一次,使用频率正常。
- 程序写得不好的“菜鸟”机器人:因为代码 bug,在一个死循环里,每毫秒向你发送一次请求,瞬间产生了成千上万次查询。
- 恶意的“攻击者”机器人:故意通过大量无意义的请求,来耗尽你信息亭的所有资源(CPU, 内存, 数据库连接),让你无法为正常用户服务。这种攻击被称为拒绝服务攻击 (Denial-of-Service, DoS)。
速率限制就是站在信息亭门口的“交通警察”。他手里拿着一个计数器,并执行一套简单的规则:
“根据规定,每位访客(每个 IP 地址)在 1 分钟内,最多只能查询 60 次。超过这个次数,我将暂时拒绝为你服务,请稍后再来!”
通过这种方式,这位“交警”有效地:
- 防止了资源滥用:无论是“菜鸟”还是“攻击者”,他们都无法在短时间内无限制地消耗你的服务资源。
- 保证了服务的可用性:为正常用户留出了宝贵的处理能力,确保他们总能得到服务。
- 提升了系统的稳定性:避免了因瞬时流量洪峰导致的系统崩溃。
NestJS 官方提供了一个非常易于使用的模块 @nestjs/throttler
,它可以让你轻松地为你的应用实现速率限制。
1. 第一步:聘请“交通警察” (ThrottlerModule
)
第一步:安装依赖
npm install @nestjs/throttler
第二步:在 AppModule
中注册并配置
// app.module.ts
import { Module } from '@nestjs/common';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
// 1. 注册 ThrottlerModule,并进行全局配置
ThrottlerModule.forRoot([
{
ttl: 60000, // 时间窗口,单位是毫秒。这里是 60 秒。
limit: 10, // 在这个时间窗口内,允许的最大请求次数。
},
]),
],
providers: [
// 2. 将 ThrottlerGuard 设置为全局守卫
// 这会让应用中的每一个接口都受到速率限制的保护
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}
代码分析:
ThrottlerModule.forRoot([...])
: 我们在这里定义了默认的全局规则——在 60 秒内,来自同一个 IP 地址的请求不能超过 10 次。{ provide: APP_GUARD, useClass: ThrottlerGuard }
: 这是将一个守卫注册为全局守卫的标准方式。ThrottlerGuard
就是由@nestjs/throttler
提供的“交通警察”。现在,他会自动地站在你应用的每一个路由前面。
工作流程:
- 一个来自 IP
123.123.123.123
的请求到达。 ThrottlerGuard
检查这个 IP 的“访问记录”(默认存储在内存中)。- 如果是 60 秒内的第 1 到 10 次请求,放行。
- 如果是第 11 次请求,
ThrottlerGuard
会抛出一个ThrottlerException
,NestJS 会自动将其转换为一个429 Too Many Requests
的 HTTP 错误响应。 - 同时,响应头中会包含
Retry-After
字段,告诉客户端应该等待多久再尝试。
2. 第二步:“特定区域”的交通管制
有时候,我们希望对不同的区域(路由)实施不同的交通管制策略。
- 登录接口 (
/auth/login
): 为了防止密码被暴力破解,规则应该更严格。 - 普通查询接口 (
/articles
): 规则可以相对宽松。 - 后台上传接口 (
/admin/upload
): 可能根本不需要限制。
我们可以使用 @Throttle()
和 @SkipThrottle()
这两个装饰器来实现。
src/auth/auth.controller.ts
import { Controller, Post } from '@nestjs/common';
import { Throttle, SkipThrottle } from '@nestjs/throttler';
@Controller('auth')
export class AuthController {
// 覆盖全局规则,设置更严格的限制
// 1 分钟内只允许 3 次尝试
@Throttle({ default: { limit: 3, ttl: 60000 } })
@Post('login')
login() {
// ...
}
// 跳过这个路由的速率限制检查
@SkipThrottle()
@Post('internal-callback')
internalCallback() {
// 这个接口可能只被内部系统调用,不需要限流
}
}
3. “交通警察”的升级:使用 Redis 存储
和 Session 一样,默认的速率限制记录存储在服务器内存中。这同样存在两个问题:
- 应用重启后记录丢失:用户可以立即重新获得完整的请求配额。
- 集群模式下不生效:如果你的应用部署了多个实例,每个实例都有自己独立的内存。来自同一个 IP 的请求可能会被分发到不同的实例上,导致限流规则被绕过。
企业级方案是将访问记录存储在一个集中的 Redis 中。
第一步:安装 Redis 存储引擎
npm install @nestjs/throttler-storage-redis
第二步:配置 ThrottlerModule
使用 Redis
// app.module.ts
import { ThrottlerModule } from '@nestjs/throttler';
import { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis';
@Module({
imports: [
ThrottlerModule.forRoot([
{
ttl: 60000,
limit: 10,
// 指定使用 Redis 作为存储
storage: new ThrottlerStorageRedisService('redis://localhost:6379'),
},
]),
],
// ...
})
export class AppModule {}
现在,所有工作进程都会向同一个 Redis 实例读写访问记录,确保了在分布式环境下速率限制的准确性和一致性。
4. 中国企业级方案的思考
@nestjs/throttler
提供的功能已经非常强大和灵活,足以应对绝大多数场景。企业级的考量,同样是将限流策略上移到 API 网关层。
为什么在 API 网关做限流更好?
- 性能:限流逻辑本身是消耗 CPU 和内存的。在网关层进行限流,意味着那些被拒绝的恶意请求根本不会到达你的 NestJS 业务应用,为后端服务节省了宝贵的资源。网关通常由更高性能的语言(如 C, Go, 或基于 Nginx+Lua)实现,处理限流的效率更高。
- 更丰富的限流维度:
- 基于用户 ID:对已登录的用户,可以根据其
userId
进行更精细的限流(普通用户 100 次/分钟,VIP 用户 1000 次/分钟)。 - 基于 API Key:对提供给第三方开发者的 API,可以根据其
API Key
进行限流。 - 全局 QPS 限制:可以限制整个服务集群的总请求速率,防止被流量打垮。
- 基于用户 ID:对已登录的用户,可以根据其
- 统一管理:和 CORS 一样,在网关层可以对所有微服务的限流策略进行统一的配置、监控和动态调整,而无需去修改和重新部署各个业务服务。
API 网关(如 Kong, APISIX)的限流配置伪代码:
# 在 Kong 的一个路由上配置 rate-limiting 插件
routes:
- name: user-service-route
paths:
- /users
plugins:
- name: rate-limiting
config:
minute: 100
policy: local # or 'cluster' or 'redis'
limit_by: ip # or 'header' (e.g., Authorization) or 'consumer'
在这种架构下,NestJS 应用本身可能只需要保留一些针对特定业务逻辑(如防止短信验证码被轰炸)的、非常细粒度的速率限制,而大部分通用的、基于流量的限流都交由网关处理。
总结
速率限制是保护你的应用免受滥用和攻击的关键防御机制。
当你想要... | 你应该使用... | 核心概念/代码 |
---|---|---|
快速为整个应用添加基础的速率限制 | 在 AppModule 中注册 ThrottlerModule 并全局启用 ThrottlerGuard | ThrottlerModule.forRoot([...]) , APP_GUARD |
为特定路由定制不同的限流规则 | 在 Controller 方法上使用 @Throttle() 或 @SkipThrottle() | 装饰器 (Decorator) |
在生产环境或集群模式下使用 | 配置 ThrottlerModule 使用 Redis 存储 | storage: new ThrottlerStorageRedisService(...) |
构建大型、高可用的微服务系统 (企业级) | 将主要的限流策略上移到 API 网关层 | 职责分离,保护后端业务服务。 |
就像城市不能没有交通警察一样,任何对外提供服务的应用程序,都应该部署一套可靠的速率限制系统。@nestjs/throttler
为你提供了一个简单而强大的起点。