Skip to content

好的,我们来探讨安全与性能领域一个至关重要的防御措施:速率限制 (Rate Limiting)。这就像是为你的应用配备一位高效的“交通警察”,他能确保在高流量时段,道路依然畅通,防止恶意拥堵和资源滥用。

为你的应用配备“交通警察”:NestJS 速率限制完全指南

想象你的应用是一家提供免费查询服务的公共信息亭。

这个信息亭(你的 API)非常受欢迎,但它的处理能力是有限的。这时,可能会出现几种情况:

  • 普通用户:每隔几秒钟来查询一次,使用频率正常。
  • 程序写得不好的“菜鸟”机器人:因为代码 bug,在一个死循环里,每毫秒向你发送一次请求,瞬间产生了成千上万次查询。
  • 恶意的“攻击者”机器人:故意通过大量无意义的请求,来耗尽你信息亭的所有资源(CPU, 内存, 数据库连接),让你无法为正常用户服务。这种攻击被称为拒绝服务攻击 (Denial-of-Service, DoS)

速率限制就是站在信息亭门口的“交通警察”。他手里拿着一个计数器,并执行一套简单的规则:

“根据规定,每位访客(每个 IP 地址)在 1 分钟内,最多只能查询 60 次。超过这个次数,我将暂时拒绝为你服务,请稍后再来!”

通过这种方式,这位“交警”有效地:

  • 防止了资源滥用:无论是“菜鸟”还是“攻击者”,他们都无法在短时间内无限制地消耗你的服务资源。
  • 保证了服务的可用性:为正常用户留出了宝贵的处理能力,确保他们总能得到服务。
  • 提升了系统的稳定性:避免了因瞬时流量洪峰导致的系统崩溃。

NestJS 官方提供了一个非常易于使用的模块 @nestjs/throttler,它可以让你轻松地为你的应用实现速率限制。

1. 第一步:聘请“交通警察” (ThrottlerModule)

第一步:安装依赖

bash
npm install @nestjs/throttler

第二步:在 AppModule 中注册并配置

typescript
// 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 提供的“交通警察”。现在,他会自动地站在你应用的每一个路由前面。

工作流程:

  1. 一个来自 IP 123.123.123.123 的请求到达。
  2. ThrottlerGuard 检查这个 IP 的“访问记录”(默认存储在内存中)。
  3. 如果是 60 秒内的第 1 到 10 次请求,放行。
  4. 如果是第 11 次请求,ThrottlerGuard 会抛出一个 ThrottlerException,NestJS 会自动将其转换为一个 429 Too Many Requests 的 HTTP 错误响应。
  5. 同时,响应头中会包含 Retry-After 字段,告诉客户端应该等待多久再尝试。

2. 第二步:“特定区域”的交通管制

有时候,我们希望对不同的区域(路由)实施不同的交通管制策略。

  • 登录接口 (/auth/login): 为了防止密码被暴力破解,规则应该更严格。
  • 普通查询接口 (/articles): 规则可以相对宽松。
  • 后台上传接口 (/admin/upload): 可能根本不需要限制。

我们可以使用 @Throttle()@SkipThrottle() 这两个装饰器来实现。

src/auth/auth.controller.ts

typescript
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 一样,默认的速率限制记录存储在服务器内存中。这同样存在两个问题:

  1. 应用重启后记录丢失:用户可以立即重新获得完整的请求配额。
  2. 集群模式下不生效:如果你的应用部署了多个实例,每个实例都有自己独立的内存。来自同一个 IP 的请求可能会被分发到不同的实例上,导致限流规则被绕过。

企业级方案是将访问记录存储在一个集中的 Redis 中。

第一步:安装 Redis 存储引擎

bash
npm install @nestjs/throttler-storage-redis

第二步:配置 ThrottlerModule 使用 Redis

typescript
// 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 网关做限流更好?

  1. 性能:限流逻辑本身是消耗 CPU 和内存的。在网关层进行限流,意味着那些被拒绝的恶意请求根本不会到达你的 NestJS 业务应用,为后端服务节省了宝贵的资源。网关通常由更高性能的语言(如 C, Go, 或基于 Nginx+Lua)实现,处理限流的效率更高。
  2. 更丰富的限流维度
    • 基于用户 ID:对已登录的用户,可以根据其 userId 进行更精细的限流(普通用户 100 次/分钟,VIP 用户 1000 次/分钟)。
    • 基于 API Key:对提供给第三方开发者的 API,可以根据其 API Key 进行限流。
    • 全局 QPS 限制:可以限制整个服务集群的总请求速率,防止被流量打垮。
  3. 统一管理:和 CORS 一样,在网关层可以对所有微服务的限流策略进行统一的配置、监控和动态调整,而无需去修改和重新部署各个业务服务。

API 网关(如 Kong, APISIX)的限流配置伪代码:

yaml
# 在 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 并全局启用 ThrottlerGuardThrottlerModule.forRoot([...]), APP_GUARD
为特定路由定制不同的限流规则在 Controller 方法上使用 @Throttle()@SkipThrottle()装饰器 (Decorator)
在生产环境或集群模式下使用配置 ThrottlerModule 使用 Redis 存储storage: new ThrottlerStorageRedisService(...)
构建大型、高可用的微服务系统 (企业级)将主要的限流策略上移到 API 网关层职责分离,保护后端业务服务。

就像城市不能没有交通警察一样,任何对外提供服务的应用程序,都应该部署一套可靠的速率限制系统。@nestjs/throttler 为你提供了一个简单而强大的起点。