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
相关产品推荐
相关产品推荐

