首页 / NestJS 入门教程 / 性能与架构

NestJS 入门教程

性能与架构

本教程共 47 篇 · 第 47 篇 · 更新于 2026-08-09 · 约 14 分钟阅读

NestJS性能优化Fastify懒加载架构设计缓存

本节目标:掌握NestJS性能优化技巧和架构设计模式,学会用Fastify提升性能、懒加载模块加速启动、缓存和队列优化响应,设计可扩展的应用架构。

教程到了最后一章,我们来聊聊性能和架构。应用跑起来只是第一步,跑得快、扛得住才是真本事。

使用Fastify提升性能

NestJS默认用Express,但Express不是最快的。Fastify比Express快将近一倍,如果性能是首要考虑,换Fastify。

安装

npm install @nestjs/platform-fastify

切换适配器

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter()
  );
  await app.listen(3000, '0.0.0.0');
}
bootstrap();

'0.0.0.0'让Fastify监听所有网络接口,不然默认只监听localhost。

注意事项

换Fastify后,有些Express的中间件不能直接用,要找Fastify的替代品。比如:

  • cookie-parser换成@fastify/cookie
  • express-session换成@fastify/secure-session
  • helmet换成@fastify/helmet

大部分常用中间件都有Fastify版本,迁移成本不算高。

Tip

如果项目已经用Express跑得好好的,没必要为了性能换Fastify。Express生态更成熟,中间件更多。新项目或者性能瓶颈明显时再考虑。

懒加载模块

默认情况下,所有模块在应用启动时就会加载。模块多了,启动时间就长。在Serverless环境里,启动慢意味着”冷启动”时间长,用户体验差。

懒加载让你按需加载模块,只在用到的时候才加载。

使用LazyModuleLoader

import { Injectable } from '@nestjs/common';
import { LazyModuleLoader } from '@nestjs/core';

@Injectable()
export class CatsService {
  constructor(private lazyModuleLoader: LazyModuleLoader) {}

  async doSomething() {
    // 动态导入模块
    const { LazyModule } = await import('./lazy.module');
    const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
    
    // 获取模块里的服务
    const { LazyService } = await import('./lazy.service');
    const lazyService = moduleRef.get(LazyService);
    
    return lazyService.doWork();
  }
}

第一次加载模块会慢一些,之后会走缓存,速度很快:

Load "LazyModule" attempt: 1
time: 2.379ms
Load "LazyModule" attempt: 2
time: 0.294ms
Load "LazyModule" attempt: 3
time: 0.303ms

适用场景

懒加载适合这些场景:

  • Serverless函数:根据请求参数加载不同的模块
  • 定时任务:不同的cron job加载不同的处理模块
  • Webhook:根据事件类型加载不同的处理器
Warning

懒加载模块里的控制器、网关、GraphQL解析器不会正常工作。因为路由注册必须在应用启动前完成。懒加载只适合纯服务类模块。

缓存策略

频繁查询数据库是性能杀手。把结果缓存起来,下次直接返回。

内存缓存

cache-manager实现简单的内存缓存:

npm install cache-manager
npm install @types/cache-manager
import { CacheModule, CacheInterceptor } from '@nestjs/common';
import { Controller, Get, UseInterceptors } from '@nestjs/common';

@Module({
  imports: [
    CacheModule.register({
      ttl: 60,    // 缓存60秒
      max: 100,   // 最多缓存100条
    }),
  ],
})
export class AppModule {}

@Controller('cats')
export class CatsController {
  @Get()
  @UseInterceptors(CacheInterceptor)
  findAll() {
    // 第一次请求会执行,之后60秒内直接返回缓存
    return this.catsService.findAll();
  }
}

CacheInterceptor自动缓存GET请求的响应。

手动控制缓存

有时候需要更精细的控制:

import { CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { Inject } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}

  async findAll(): Promise<Cat[]> {
    const cacheKey = 'cats_all';
    const cats = await this.cache.get(cacheKey);
    
    if (cats) {
      return cats;
    }
    
    const result = await this.db.cats.find();
    await this.cache.set(cacheKey, result, 60);
    
    return result;
  }

  async create(cat: CreateCatDto) {
    const result = await this.db.cats.create(cat);
    // 创建后清除缓存
    await this.cache.del('cats_all');
    return result;
  }
}

Redis缓存

生产环境用Redis做缓存,支持分布式共享:

import { redisStore } from 'cache-manager-redis-store';

CacheModule.register({
  store: await redisStore({
    host: 'localhost',
    port: 6379,
  }),
  ttl: 60,
})

队列处理

有些操作很耗时,比如发邮件、生成报表。同步处理会让用户等很久。把这些任务扔进队列,后台慢慢处理。

使用Bull队列

npm install @nestjs/bull bull
npm install -D @types/bull
import { BullModule } from '@nestjs/bull';

@Module({
  imports: [
    BullModule.forRoot({
      redis: {
        host: 'localhost',
        port: 6379,
      },
    }),
    BullModule.registerQueue({
      name: 'email',
    }),
  ],
})
export class AppModule {}

生产者:添加任务到队列

import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class EmailService {
  constructor(@InjectQueue('email') private emailQueue: Queue) {}

  async sendWelcomeEmail(userId: string) {
    await this.emailQueue.add('welcome', { userId });
    // 立即返回,不等邮件发完
  }
}

消费者:处理队列任务

import { Processor, Process } from '@nestjs/bull';
import { Job } from 'bull';

@Processor('email')
export class EmailProcessor {
  @Process('welcome')
  async handleWelcomeEmail(job: Job) {
    const { userId } = job.data;
    // 发送邮件的逻辑
    console.log(`Sending welcome email to user ${userId}`);
  }
}

队列的好处:

  • 异步处理:用户不用等
  • 失败重试:失败了自动重试
  • 限流:控制并发数,不会压垮第三方服务
  • 削峰填谷:高峰期排队的任务,低峰期慢慢处理

数据库优化

数据库往往是性能瓶颈。几个优化方向:

索引优化

给常用查询字段加索引:

@Entity()
export class User {
  @Index()  // 加索引
  @Column()
  email: string;

  @Index()
  @Column()
  createdAt: Date;
}

分页查询

别一次查所有数据,用分页:

async findAll(page: number, limit: number) {
  return this.userRepository.find({
    skip: (page - 1) * limit,
    take: limit,
  });
}

连接池

数据库连接是宝贵的,用连接池复用:

TypeOrmModule.forRoot({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  // 连接池配置
  extra: {
    max: 20,           // 最大连接数
    min: 5,            // 最小连接数
    idleTimeoutMillis: 30000,
  },
})

避免N+1查询

用TypeORM的relations选项预加载关联数据:

// 不好:N+1查询
const users = await this.userRepository.find();
for (const user of users) {
  user.posts = await this.postRepository.find({
    where: { userId: user.id }
  });
}

// 好:一次查询
const users = await this.userRepository.find({
  relations: ['posts'],
});

架构模式

分层架构

NestJS天然支持分层架构:

Controller → Service → Repository → Database
     ↓           ↓
   Guard      Interceptor
     ↓           ↓
   Pipe       Filter

每层各司其职:

  • Controller:接收请求,返回响应
  • Service:业务逻辑
  • Repository:数据访问
  • Guard:权限控制
  • Interceptor:请求/响应拦截
  • Pipe:数据验证和转换
  • Filter:异常处理

模块化设计

按业务领域划分模块,而不是按技术层:

src/
├── users/          # 用户模块
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── users.repository.ts
├── orders/         # 订单模块
│   ├── orders.module.ts
│   ├── orders.controller.ts
│   ├── orders.service.ts
│   └── orders.repository.ts
├── payments/       # 支付模块
│   └── ...
└── shared/         # 共享模块
    ├── auth/
    ├── cache/
    └── logger/

依赖倒置

高层模块不依赖低层模块,都依赖抽象:

// 定义接口
export interface IUserRepository {
  findById(id: number): Promise<User>;
  save(user: User): Promise<User>;
}

// 实现接口
@Injectable()
export class TypeOrmUserRepository implements IUserRepository {
  constructor(
    @InjectRepository(User)
    private repo: Repository<User>
  ) {}

  async findById(id: number) {
    return this.repo.findOne({ where: { id } });
  }

  async save(user: User) {
    return this.repo.save(user);
  }
}

// 服务依赖接口,不依赖具体实现
@Injectable()
export class UsersService {
  constructor(
    @Inject('IUserRepository')
    private userRepo: IUserRepository
  ) {}
}

好处是随时能换实现,测试时也好Mock。

事件驱动

用事件解耦模块间的依赖:

// 定义事件
export class UserCreatedEvent {
  constructor(public readonly userId: string) {}
}

// 发布事件
@Injectable()
export class UsersService {
  constructor(private eventEmitter: EventEmitter2) {}

  async create(dto: CreateUserDto) {
    const user = await this.repo.save(dto);
    this.eventEmitter.emit('user.created', new UserCreatedEvent(user.id));
    return user;
  }
}

// 监听事件
@Injectable()
export class EmailListener {
  @OnEvent('user.created')
  handleUserCreated(event: UserCreatedEvent) {
    // 发送欢迎邮件
  }
}

用户服务不需要知道谁在监听,邮件服务不需要知道用户是怎么创建的。松耦合,好维护。

性能监控

优化之前先测量,不然不知道优化了啥。

请求耗时

用拦截器记录每个请求的耗时:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const now = Date.now();
    const request = context.switchToHttp().getRequest();
    
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - now;
        console.log(`${request.method} ${request.url} - ${duration}ms`);
      }),
    );
  }
}

内存使用

监控内存使用,防止内存泄漏:

setInterval(() => {
  const usage = process.memoryUsage();
  console.log({
    rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
    heap: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
  });
}, 60000);

APM工具

生产环境用APM工具(如New Relic、Datadog、SkyWalking)监控:

  • 请求响应时间
  • 错误率
  • 数据库查询耗时
  • 外部服务调用耗时

架构演进

单体应用

刚开始用单体应用,所有模块在一个进程里。简单、好部署、好调试。

垂直拆分

流量大了,把耗资源的模块拆出来单独部署:

┌─────────────┐     ┌─────────────┐
│  API Gateway │────▶│  User Service│
└──────┬──────┘     └─────────────┘

       │            ┌─────────────┐
       └───────────▶│ Order Service│
                    └─────────────┘

微服务

继续拆分,每个业务领域一个微服务。NestJS的微服务支持让你平滑过渡。

Tip

别一上来就搞微服务。单体应用能扛住就别拆。微服务的复杂度远超单体,运维成本也高。等业务真的需要了再拆。

小结

这一章学了NestJS性能优化和架构设计:

Fastify:比Express快一倍,适合性能敏感的场景。

懒加载:按需加载模块,加速Serverless冷启动。

缓存:用cache-manager缓存频繁查询的数据,Redis支持分布式共享。

队列:用Bull处理耗时任务,异步、重试、限流。

数据库优化:索引、分页、连接池、避免N+1查询。

架构模式:分层架构、模块化设计、依赖倒置、事件驱动。

性能监控:拦截器记录耗时,APM工具全面监控。

架构演进:单体 → 垂直拆分 → 微服务,按需演进。

性能优化不是一蹴而就的。先跑起来,再跑快。找到瓶颈,针对性优化。架构设计也不是一步到位的。先简单,再复杂。业务发展到哪一步,架构就演进到哪一步。

恭喜你完成了整个NestJS教程!从基础概念到高级技巧,从测试到部署,从性能到架构,你已经掌握了NestJS开发的核心知识。接下来就是动手实践,在真实项目中磨练技能。加油!

上一篇
部署上线
下一篇
已经是最后一篇啦