错误的 Singleton 实现导致我们在 NestJS 中的 Redis 连接崩溃
Source: Dev.to
单例模式
如果需要回顾一下:单例模式 确保一个类在整个应用生命周期中只有唯一的实例。
- 该实例在应用启动时创建一次。
- 所有其他服务共享这唯一实例,而不是每次都创建新实例,这对数据库或消息中间件等资源密集型任务至关重要。
NestJS 中的依赖注入 (DI)
单例行为与依赖注入紧密耦合。在 NestJS 中,Singleton 是 DI 的默认作用域。
- 在启动阶段,NestJS 会在 IoC(控制反转)容器 中注册这些依赖。
- 要使用服务,只需在类构造函数中注入,NestJS 会处理其余工作。
“遗留”错误:DI 出错时
在 NestJS 中,@Injectable() 装饰器默认使用单例作用域。如果需要让服务在整个应用中可用(如 Config、Logger 或共享客户端),可以使用 @Global() 装饰器。
我们遇到的问题
尽管服务已经标记为单例,遗留代码仍在多个模块的 providers 数组中手动重新声明该服务,并且不必要地过度使用 @Inject() 注解。
结果: 开发者形成了“复制‑粘贴”模式。每当一个新服务需要与另一个微服务通信时,都会意外触发 新实例 的 ClientProxy。这导致 Redis 活跃连接数量激增,形成巨大的 瓶颈,并拖慢整个基础设施。
解决方案
思路很直接,但需要彻底清理:
- 集中模块 – 创建专门的
ClientProxyModule来存放ClientProxyService。 - 全局装饰器 – 为该模块添加
@Global()装饰器,使其在根层级只初始化一次。 - 重构 – 删除其他服务中多余的 provider 声明和不必要的
@Inject()注解,恢复使用标准的构造函数注入。
调试小技巧
如何判断单例是否处理正确?这里有两个快速技巧:
- 构造函数日志 – 在服务的
constructor中加入console.log。重启应用后,如果日志出现 多次,说明出现了实例泄漏。它应该只在启动时打印一次。 - Getter 日志 – 如果使用 getter 方法,在其中加入日志,以监控服务被访问的具体时间和位置。
结语
无论你使用 NestJS、Spring Boot 还是 .NET,单例模式 的核心原则始终重要。
- 理解作用域 – 不要盲目复制‑粘贴。了解框架如何管理实例。
- 审计连接 – 无论是单体还是微服务,“盲目注入”都可能导致资源耗尽。
检查一下你当前的项目——是否在不经意间滥用注入?让我们保持连接整洁,系统高速。
祝编码愉快! 🚀