
pm2 集群模式会启动多个独立 node.js 进程,而 nestjs 的“单例服务”仅在单个进程内全局唯一;跨进程间无法自动共享状态,直接依赖内存属性将导致数据不一致。
pm2 集群模式会启动多个独立 node.js 进程,而 nestjs 的“单例服务”仅在单个进程内全局唯一;跨进程间无法自动共享状态,直接依赖内存属性将导致数据不一致。
当你使用 pm2 start app.js -i max 启动 NestJS 应用时,PM2 会创建多个工作进程(例如 CPU 核心数个),每个进程都拥有独立的 V8 实例、堆内存和事件循环。这意味着:
- ✅ @Injectable({ scope: Scope.SINGLETON })(默认)的服务在每个进程内确实是单例;
- ❌ 但不同进程间的同一服务实例完全隔离——修改 this.counter++ 在进程 A 中,不会影响进程 B 中的 counter 值;
- ⚠️ 若你的业务逻辑依赖此类跨请求/跨用户共享的内存状态(如计数器、临时缓存、会话聚合等),集群部署后将出现不可预测的行为(如重复执行、状态丢失、统计失真)。
为什么不能靠“单例”实现跨进程共享?
NestJS 的单例生命周期由 DI 容器管理,容器作用域绑定于当前 Node.js 进程。PM2 Cluster 模式下无进程间通信(IPC)机制,默认不共享任何内存或对象引用。即使使用 process.send() 或 cluster 模块手动通信,也需自行设计同步逻辑,复杂度陡增——这恰恰违背了你“不引入 Redis 等外部依赖”的初衷。
轻量可行方案:进程内缓存 + 显式降级策略
若无需强一致性,仅需缓解高频读取、减少重复计算,推荐采用 内存缓存 + 过期控制,兼顾简单性与实用性:
// cache.service.ts
import { Injectable } from '@nestjs/common';
import * as NodeCache from 'node-cache';
@Injectable()
export class LocalCacheService {
private readonly cache = new NodeCache({ stdTTL: 300 }); // 默认5分钟过期
set(key: string, value: any, ttl?: number) {
this.cache.set(key, value, ttl);
}
get<T>(key: string): T | undefined {
return this.cache.get(key);
}
del(key: string) {
this.cache.del(key);
}
}// counter.service.ts(示例:带本地缓存的计数器)
@Injectable()
export class CounterService {
constructor(private readonly cacheService: LocalCacheService) {}
increment(key: string): number {
const current = this.cacheService.get<number>(key) || 0;
const next = current + 1;
this.cacheService.set(key, next, 60); // 60秒后自动失效
return next;
}
}✅ 优势:零外部依赖、开箱即用、低延迟;
⚠️ 注意:仍属“进程局部视图”,各实例缓存独立,适用于最终一致性场景(如仪表盘实时指标、API 限流令牌桶的近似计数);
❌ 不适用:强一致性要求场景(如库存扣减、分布式锁、幂等事务状态)。
总结建议
- 不要假设单例 = 全局共享:NestJS 单例 ≠ 分布式单例;
- 优先评估业务容忍度:是否允许短暂不一致?能否接受按进程分片统计?
- 轻量升级路径:从 node-cache → Redis(当一致性成为硬需求时)是平滑演进;
- 避免反模式:禁用 pm2 -i 1 强行单进程(牺牲水平扩展能力),也不建议用 fs.writeFileSync 模拟共享内存(性能差、易崩溃)。
真正的分布式状态管理终需外部协调系统,但在 MVP 或内部工具阶段,合理利用进程内缓存,配合清晰的文档说明“此计数为估算值”,往往是更务实的技术选型。


















