• NestJS性能优化之-循环依赖优化
  • 发布于 3天前
  • 23 热度
    0 评论
  • 乱人心
  • 0 粉丝 50 篇博客
  •   
大家优化 NestJS 性能时,第一反应往往是:数据库调优、加缓存、改请求解析逻辑。我也一样。直到有一天,我发现真正拖慢我项目的,并不是这些“显眼的地方”,而是一个一直被我忽略的老问题——循环依赖。

藏在眼皮底下的性能杀手
当时我的项目冷启动总是慢得离谱,第一次访问接口也会卡半天。日志里没报错,性能监控也没大问题,但就是“不顺”。直到我在模块结构里,发现了这个“循环引用”:
// user.module.ts
@Module({
  imports: [AuthModule],
  providers: [UserService],
})
export class UserModule {}

// auth.module.ts
@Module({
  imports: [UserModule],
  providers: [AuthService],
})
export class AuthModule {}
.UserModule 引入了 AuthModule
.而 AuthModule 又反过来引入 UserModule。
.NestJS 虽然提供了 forwardRef() 这种临时补丁,但问题并没有真正解决,只是勉强能跑。

循环依赖的危害,比你想象的更狠
启动时间变长:Nest 在解析依赖时会“卡壳”。
内存浪费:模块被反复加载、常驻内存。
依赖注入不稳定:初始化阶段拿到的可能是 undefined。
线上事故隐患:偶发性 bug 难以追踪和复现。
我专门做了个对比测试:去掉循环依赖后,冷启动速度直接提升 **40%**,接口延迟肉眼可见地下降。

我的解决方案:重构模块架构
这次优化,我做了三件事。
1. 把通用服务抽到 CoreModule
@Module({
  providers: [JwtService, ConfigService],
  exports: [JwtService, ConfigService],
})
export class CoreModule {}
以后需要这些服务的地方,统一引入 CoreModule,不再模块间互相引用。

2. 按“职责”而不是“功能”拆分模块
以前是这样的:
user/
  user.controller.ts
  user.service.ts
  user.module.ts
auth/
  auth.controller.ts
  auth.service.ts
  auth.module.ts
我改成这样:
services/
  auth.service.ts
  user.service.ts
controllers/
  auth.controller.ts
  user.controller.ts
modules/
  auth.module.ts
  user.module.ts
边界更清晰、导入更简单,自然就不会出现“互相依赖”的死循环。

3. 用接口替代直接依赖实现
我把服务之间的调用换成基于接口的依赖注入:
export interface UserStore {
  findUserByEmail(email: string): Promise<User>;
}
AuthService 依赖的是 UserStore 接口,而不是 UserService 具体实现,这样就避免了双向绑定。

forwardRef 不是万能解药
forwardRef(() => Module) 只能算临时方案。
如果你经常用它,八成是你的模块设计有问题。
如果你不得不写 forwardRef,我的建议:
问问自己,这两个模块真的必须互相引用吗?
大多数时候,答案是“不必”。

🏁 最终成果
仅仅是消灭循环依赖 + 梳理模块边界,我的 NestJS 项目就完成了一次“减负”:
.冷启动速度提升 40%
.接口首次响应延迟明显降低

.依赖注入稳定性提升


结论

架构优化,不只是让代码更干净,还能让性能飞起来。

如果你也在优化 NestJS,不妨看看你的模块导入关系。
有时候,真正的性能瓶颈,就藏在那些你“习惯了”的结构里。
用户评论