You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS TCP微服务PM2集群模式下优雅重启问题求助

NestJS TCP微服务PM2集群模式优雅重启解决方案

问题场景

应用结构:

  • App1:NestJS HTTP服务,监听8080端口,接收用户请求,鉴权后调用App2获取计算结果
  • App2:NestJS TCP微服务,监听8081端口,核心代码如下:
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
  AppModule,
  {
    transport: Transport.TCP,
    options: { host: '127.0.0.1', port: 8081 }
  },
);
await app.listen();

部署问题:App2通过PM2集群模式部署后,执行pm2 reload App2时,新请求仍会被分发到正在启动/关闭的实例,导致请求处理失败。原因是PM2无法识别Node net套接字的通信特性,重启流程中未正确过滤非健康实例。

解决步骤

1. 给App2添加优雅关闭逻辑

让App2监听SIGINT/SIGTERM信号,收到信号后先关闭TCP服务,停止接收新连接,等待现有请求处理完成后再退出。修改App2的main.ts:

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
      options: { host: '127.0.0.1', port: 8081 }
    },
  );

  // 处理终止信号,实现优雅关闭
  process.on('SIGINT', async () => {
    await app.close();
    process.exit(0);
  });

  process.on('SIGTERM', async () => {
    await app.close();
    process.exit(0);
  });

  await app.listen();
  // 通知PM2实例已就绪
  process.send?.('ready');
}
bootstrap();

app.close()会自动关闭TCP服务器,拒绝新连接,同时等待正在处理的请求完成后再终止进程。

2. 配置PM2优雅重启参数

在PM2的配置文件(如ecosystem.config.js)中,为App2添加以下配置,确保PM2只向就绪实例分发请求:

module.exports = {
  apps: [
    {
      name: 'App2',
      script: 'dist/main.js',
      instances: 'max',
      exec_mode: 'cluster',
      // 优雅重启核心配置
      kill_timeout: 5000, // 发送SIGTERM后等待5秒再强制终止
      wait_ready: true, // 等待实例发送ready信号才标记为就绪
      listen_timeout: 10000, // 实例就绪超时时间(10秒)
      shutdown_with_message: true, // 关闭时通知PM2状态变更
    }
  ]
};

wait_ready配合App2发送的ready信号,能让PM2精准识别实例是否完全启动,避免将请求分给正在初始化的实例。

3. 给App1的微服务客户端添加健康检查与负载均衡

因为PM2集群模式下多个App2实例不能监听同一端口,先修改App2的端口配置,让每个实例使用独立端口:

// App2 main.ts
const basePort = parseInt(process.env.PORT || '8081');
const instanceId = parseInt(process.env.NODE_APP_INSTANCE || '0');

const app = await NestFactory.createMicroservice<MicroserviceOptions>(
  AppModule,
  {
    transport: Transport.TCP,
    options: { 
      host: '127.0.0.1', 
      port: basePort + instanceId // 实例0用8081,实例1用8082,以此类推
    }
  },
);

然后在App1中配置客户端,启用负载均衡和健康检查,确保只向健康的App2实例发送请求:

// App1 模块配置
import { ClientsModule, Transport, ClientProxyLoadBalancingStrategies } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'APP2_SERVICE',
        transport: Transport.TCP,
        options: {
          // 列出所有App2实例的地址
          urls: [
            'tcp://127.0.0.1:8081',
            'tcp://127.0.0.1:8082',
            // 根据实例数量补充
          ],
          // 轮询负载均衡
          loadBalancingStrategy: ClientProxyLoadBalancingStrategies.ROUND_ROBIN,
          // 启用健康检查
          healthCheck: true,
          retryAttempts: 3,
          retryDelay: 1000,
        },
      },
    ]),
  ],
})
export class AppModule {}

健康检查会定期探测App2实例的状态,自动剔除已关闭或未就绪的实例,确保请求只分发到正常运行的实例。

效果验证

执行pm2 reload App2后,PM2会逐个替换实例:先启动新实例,等待其发送ready信号后,再向旧实例发送SIGTERM信号,旧实例处理完现有请求后退出。整个过程中,App1的客户端只会向健康实例发送请求,不会出现请求失败的情况。

内容的提问来源于stack exchange,提问作者Dr. DS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 05:25:06