
pm2 集群模式会启动多个独立 node.js 进程,而 nestjs 的单例服务仅在单个进程中全局唯一;进程间内存隔离导致服务状态无法自动共享,直接依赖单例属性存储跨请求/跨实例数据将引发不一致问题。
pm2 集群模式会启动多个独立 node.js 进程,而 nestjs 的单例服务仅在单个进程中全局唯一;进程间内存隔离导致服务状态无法自动共享,直接依赖单例属性存储跨请求/跨实例数据将引发不一致问题。
当你使用 pm2 start ecosystem.config.js --env production 启动 NestJS 应用并启用 instances: 'max' 或指定数值时,PM2 会 fork 出多个完全隔离的进程。每个进程拥有独立的 V8 堆内存、事件循环和模块缓存——这意味着:
- ✅ @Injectable({ scope: Scope.SINGLETON }) 服务在单个进程内仍是真正的单例(同一 Class 实例被所有请求复用);
- ❌ 但不同进程中的同名服务实例互不可见、互不通信,其属性(如 this.counter = 0)各自维护,修改不会同步。
例如,以下代码在集群下将产生非预期行为:
// counter.service.ts
@Injectable()
export class CounterService {
private count = 0;
increment(): number {
return ++this.count; // 每个进程独立计数,非全局累加
}
getCount(): number {
return this.count;
}
}
若你期望“全应用统一计数”,该设计在 PM2 cluster 下必然失效——访问 A 进程 API 增加到 5,B 进程仍显示 0。
✅ 推荐轻量级解决方案(无需 Redis)
避免引入外部依赖,同时保障数据一致性,可采用以下两种实践:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
1. 使用 CacheModule + 内存缓存(推荐)
NestJS 官方 CacheModule 默认基于 node-cache,支持 TTL 和进程内共享。适用于读多写少、容忍短暂不一致的场景:
npm install @nestjs/cache-manager cache-manager
// app.module.ts
import { CacheModule, Module } from '@nestjs/common';
import { CacheService } from './cache.service';
@Module({
imports: [
CacheModule.register({
ttl: 30000, // 30s
max: 1000,
}),
],
providers: [CacheService],
})
export class AppModule {}
// cache.service.ts
@Injectable()
export class CacheService {
constructor(private readonly cacheManager: Cache) {}
async incrementCounter(key: string): Promise<number> {
const current = await this.cacheManager.get<number>(key) || 0;
const next = current + 1;
await this.cacheManager.set(key, next, { ttl: 30000 });
return next;
}
}</number></number>
⚠️ 注意:此方案仍为进程内缓存,未解决跨进程共享。若需真正全局状态,请继续阅读下一条。
2. 利用 PM2 的 sharedMemory(高级但零依赖)
PM2 提供实验性 pm2-shared-memory 模块(需手动启用),允许进程间通过底层共享内存通信。但稳定性与调试成本较高,不推荐生产环境首选。
? 关键结论与建议
- 不要将业务关键状态(如用户会话、订单锁、计数器)直接存于单例服务属性中用于集群部署;
- 优先评估是否真需全局状态:多数场景可通过无状态设计(如将状态存入数据库、客户端 Token、或 HTTP Header 透传)规避;
- 若必须进程间共享且拒绝 Redis:可考虑轻量级本地文件锁(fs.writeFileSync + JSON)、或 SQLite 内存 DB(sqlite3 with :memory: 不适用,但磁盘 SQLite 可行)——但性能与并发需严格测试;
- 最终权衡:为可靠性适度引入 Redis,远胜于自建脆弱共享机制。redis + @nestjs-modules/ioredis 仅需 3 行配置,是业界标准解法。
简言之:NestJS 单例 ≠ 分布式单例。集群不是“增强版单进程”,而是“多个单进程副本”——设计之初就应按分布式思维建模。










