如何正确停止 Node API 集群服务器?

5

我正在学习如何在我的Koa + TypeScript API中使用集群。我找到了一篇博客文章,并从中参考了一些内容。

// server.ts

import {app} from './app' // Koa

export function server() {
  const http = app.listen(8000, async () => {
    console.info(`Listening at 8000`)
  })
  return http
}

// main.ts

async function main() {
  try {
    // Create or migrate
    await knex.migrate.latest()
    return server()
  } catch (err) {
    logger.error('Failed to start: ', err.stack)
    process.exit(1)
  }
}

// Clustering
const numCPUs = cpus().length

if (cluster.isMaster) {
  logger.info(`${numCPUs} CPUs available`)
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork()
  }

  cluster.on('online', worker => {
    logger.info(`Worker ${worker.process.pid} is online`)
  })

  cluster.on('disconnect', worker => {
    logger.error(`${worker.process.pid} disconnect!`)
    cluster.fork()
  })

  /** I do not think I need this block, right?
   *
  cluster.on('exit', (worker, code, signal) => {
    logger.info(
      `Worker ${worker.process.pid} died with code: ${code} and signal: ${signal}`,
    )
    logger.info('Starting a new worker...')
    cluster.fork()
  })
   */
} else {
  main()
}

我有一个优雅的关闭函数:

// Graceful shutdown
async function shutdown() {
  try {
    await knex.destroy().then(() => {
      logger.warn('Destroyed Knex pool')
    })
    // Make sure we close down within 30 seconds
    const killtimer = setTimeout(() => {
      process.exit(1)
    }, 30000)
    // But don't keep the process open just for that!
    killtimer.unref()
    server().close(() => {
      logger.warn('Closed server')
      process.exit()
    })
    // Let the maste know w're dead.
    // return cluster.worker.disconnect()
  } catch (e) {
    logger.error(`Error during disconnection: ${e.stack}`)
    process.exit(1)
  }
}

function awaitHardStop() {
  const timeout = SHUTDOWN_TIMEOUT ? +SHUTDOWN_TIMEOUT : 1000 * 30
  return setTimeout(() => {
    logger.error(`Process did not terminate within ${timeout}ms. Stopping now!`)
    process.nextTick(process.exit(1))
  }, timeout)
}

// Catch unhandling unexpected exceptions
process.on('uncaughtException', e => {
  logger.error(`Uncaught exception: ${e.message}`)
  process.nextTick(process.exit(1))
})

// Catch unhandling rejected promises
process.on('unhandledRejection', (reason, promise) => {
  logger.error(`Unhandled rejection at ${promise}, reason: ${reason}`)
  process.nextTick(process.exit(1))
})

process.on('SIGINT', async () => {
  logger.warn(`[ SIGNAL ] - SIGINT`)
  const timer = awaitHardStop()
  await shutdown()
  clearTimeout(timer)
})

process.on('SIGTERM', async () => {
  logger.warn(`[ SIGNAL ] - SIGTERM`)
  const timer = awaitHardStop()
  await shutdown()
  clearTimeout(timer)
})

process.on('SIGUSR2', async () => {
  logger.warn(`[ SIGNAL ] - SIGUSR2`)
  const timer = awaitHardStop()
  await shutdown()
  clearTimeout(timer)
})

问题是当我使用 Ctrl+c 停止时,会出现以下情况:

Error: listen EADDRINUSE: address already in use :::8000
    at Server.setupListenHandle [as _listen2] (net.js:1313:16)
    at listenInCluster (net.js:1361:12)
    at Server.listen (net.js:1447:7)
    at Application.listen (/home/usr/workspace/koa-ts/app/node_modules/koa/lib/application.js:82:19)
    at Object.server (/home/usr/workspace/koa-ts/app/src/core/server.ts:7:20)
    at /home/usr/workspace/koa-ts/app/src/main.ts:71:5
    at Generator.next (<anonymous>)
    at fulfilled (/home/usr/workspace/koa-ts/app/src/main.ts:24:58)
{"level":50,"time":1597173528053,"pid":284878,"hostname":"usr-PC","msg":"Uncaught exception: listen EADDRINUSE: address already in use :::8000"}

因此,我尝试在 main.ts 中在 server().close() 下面添加了这一行:

    ...
    // Let the maste know w're dead.
    return cluster.worker.disconnect()
    ...

当我按下Ctrl+c时,我得到了以下内容:

Error: listen EADDRINUSE: address already in use :::8000
    at Server.setupListenHandle [as _listen2] (net.js:1313:16)
    at listenInCluster (net.js:1361:12)
    at Server.listen (net.js:1447:7)
    at Application.listen (/home/usr/workspace/koa-ts/app/node_modules/koa/lib/application.js:82:19)
    at Object.server (/home/usr/workspace/koa-ts/app/src/core/server.ts:7:20)
    at /home/usr/workspace/koa-ts/app/src/main.ts:71:5
    at Generator.next (<anonymous>)
    at fulfilled (/home/usr/workspace/koa-ts/app/src/main.ts:24:58)
{"level":50,"time":1597173528053,"pid":284878,"hostname":"usr-PC","msg":"Uncaught exception: listen EADDRINUSE: address already in use :::8000"}
{"level":50,"time":1597173554364,"pid":285843,"hostname":"usr-PC","msg":"Error during disconnection: TypeError: Cannot read property 'disconnect' of undefined\n    at /home/usr/workspace/koa-ts/app/src/main.ts:76:27\n    at Generator.next (<anonymous>)\n    at fulfilled (/home/usr/workspace/koa-ts/app/src/main.ts:24:58)"}
{"level":50,"time":1597173554364,"pid":285843,"hostname":"usr-PC","msg":"Error during disconnection: TypeError: Cannot read property 'disconnect' of undefined\n    at /home/usr/workspace/koa-ts/app/src/main.ts:76:27\n    at Generator.next (<anonymous>)\n    at fulfilled (/home/usr/workspace/koa-ts/app/src/main.ts:24:58)"}
llsi/app/src/main.ts:24:58)"}
{"level":50,"time":1597173997176,"pid":287724,"hostname":"usr-PC","msg":"Error during disconnection: TypeError: Cannot read property 'disconnect' of undefined\n    at /home/usr/workspace/koa-ts/app/src/main.ts:76:27\n    at Generator.next (<anonymous>)\n    at fulfilled (/home/usr/workspace/koa-ts/app/src/main.ts:24:58)"}

这正常吗,还是我做错了什么?

我正在使用Node(v12.18.3)。

参考:http://bisaga.com/blog/programming/create-a-node-cluster-with-koa-and-typescript/

1个回答

1

在Node.js退出之前执行清理操作

看看这里提出的建议。我也正在寻找一种在任何形式的进程终止时进行清理的方法...你应该仅在cleanup为true时执行终止操作。此外,我建议您查看进程管理器。例如,可以查看PM2,它允许您轻松地在集群中启动应用程序,并为您管理不同的工作进程!


它真的很好用。非常感谢!不过我还有一个问题,我能完全用它替换我的大量优雅关闭函数吗?对于PM2,我以前使用过它。但现在我正在使用Docker,我看到人们不建议在Docker中运行PM2。您会在Docker中使用PM2吗? - Swix
1
很抱歉很难说...从未在pm2和docker中遇到过这个问题。但也许有其他的选择?至于能否替换它... 在清理期间,您应该能够调用任何需要清理的东西。很难说确切地可以删除什么。进行实验? - stefantigro
非常感谢。我会试一下的。 :) - Swix
1
没问题!很抱歉我不能提供更多帮助。 - stefantigro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接