如何在其进程被杀死时如何正常关闭我的Express服务器?

时间:2017-03-24 15:48:54

标签: node.js express

在生产中运行我的Express应用程序时,我希望在其进程被终止时正常关闭服务器(即发送SIGTERM或SIGINT)。

以下是我的代码的简化版本:

const express = require('express');

const app = express();

app.get('/', (req, res) => res.json({ ping: true }));

const server = app.listen(3000, () => console.log('Running…'));

setInterval(() => server.getConnections(
    (err, connections) => console.log(`${connections} connections currently open`)
), 1000);

process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);

function shutDown() {
    console.log('Received kill signal, shutting down gracefully');
    server.close(() => {
        console.log('Closed out remaining connections');
        process.exit(0);
    });

    setTimeout(() => {
        console.error('Could not close connections in time, forcefully shutting down');
        process.exit(1);
    }, 10000);
}

当我在浏览器中运行它并调用URL http://localhost:3000/时,setInterval函数中的日志语句将继续打印“1个当前打开的连接”,直到我实际关闭浏览器窗口。即使关闭标签,显然也会保持连接处于打开状态。

所以,我按Ctrl + C杀了我的服务器,它将进入超时并在10秒后打印“无法关闭连接”,同时继续打印“1个连接打开”。

只有在我杀死进程之前关闭浏览器窗口时,才会收到“已关闭的剩余连接”消息。

我在这里缺少什么?正常关闭Express服务器的正确方法是什么?

6 个答案:

答案 0 :(得分:27)

如果有人有兴趣,我自己找到了一个解决方案(很想听到评论中的反馈)。

我为服务器上打开的连接添加了一个侦听器,在数组中存储对这些连接的引用。关闭连接后,它们将从阵列中删除。

当服务器被终止时,通过调用其end方法关闭每个连接。对于某些浏览器(例如Chrome),这还不够,所以在超时后,我会在每个连接上调用destroy

const express = require('express');

const app = express();

app.get('/', (req, res) => res.json({ ping: true }));

const server = app.listen(3000, () => console.log('Running…'));

setInterval(() => server.getConnections(
    (err, connections) => console.log(`${connections} connections currently open`)
), 1000);

process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);

let connections = [];

server.on('connection', connection => {
    connections.push(connection);
    connection.on('close', () => connections = connections.filter(curr => curr !== connection));
});

function shutDown() {
    console.log('Received kill signal, shutting down gracefully');
    server.close(() => {
        console.log('Closed out remaining connections');
        process.exit(0);
    });

    setTimeout(() => {
        console.error('Could not close connections in time, forcefully shutting down');
        process.exit(1);
    }, 10000);

    connections.forEach(curr => curr.end());
    setTimeout(() => connections.forEach(curr => curr.destroy()), 5000);
}

答案 1 :(得分:10)

您遇到的问题是,所有现代浏览器都会针对多个请求重复使用单个连接。这称为保持活动连接

处理此问题的正确方法是监控所有新连接和请求,并跟踪每个连接的状态(现在是空闲还是活动)。然后,您可以强制关闭所有空闲连接,并确保在处理当前请求后关闭活动连接。

我实施了@moebius/http-graceful-shutdown模块,专门用于优雅地关闭Express应用程序和节点服务器。可悲的是,也没有Express,Node本身也没有内置的这个功能。

以下是它如何与任何Express应用程序一起使用:

const express = require('express');
const GracefulShutdownManager = require('@moebius/http-graceful-shutdown').GracefulShutdownManager;


const app = express();

const server = app.listen(8080);

const shutdownManager = new GracefulShutdownManager(server);

process.on('SIGTERM', () => {
  shutdownManager.terminate(() => {
    console.log('Server is gracefully terminated');
  });
});

随意退房the module,GitHub页面有更多详情。

答案 2 :(得分:2)

Express(https://github.com/godaddy/terminus)的创建者推荐了一个开源项目https://expressjs.com/en/advanced/healthcheck-graceful-shutdown.html

使用终端的基本示例:

const http = require('http');
const express = require('express');
const terminus = require('@godaddy/terminus');

const app = express();

app.get('/', (req, res) => {
  res.send('ok');
});

const server = http.createServer(app);

function onSignal() {
  console.log('server is starting cleanup');
  // start cleanup of resource, like databases or file descriptors
}

async function onHealthCheck() {
  // checks if the system is healthy, like the db connection is live
  // resolves, if health, rejects if not
}

terminus(server, {
  signal: 'SIGINT',
   healthChecks: {
    '/healthcheck': onHealthCheck,
  },
  onSignal
});

server.listen(3000);

在需要服务器生命周期回调(例如,从服务注册表中注销实例等)的情况下,终端有很多选择:

const options = {
  // healtcheck options
  healthChecks: {
    '/healthcheck': healthCheck    // a promise returning function indicating service health
  },

  // cleanup options
  timeout: 1000,                   // [optional = 1000] number of milliseconds before forcefull exiting
  signal,                          // [optional = 'SIGTERM'] what signal to listen for relative to shutdown
  signals,                          // [optional = []] array of signals to listen for relative to shutdown
  beforeShutdown,                  // [optional] called before the HTTP server starts its shutdown
  onSignal,                        // [optional] cleanup function, returning a promise (used to be onSigterm)
  onShutdown,                      // [optional] called right before exiting

  // both
  logger                           // [optional] logger function to be called with errors
};

答案 3 :(得分:1)

尝试NPM express-graceful-shutdown module,正常关闭将允许包括数据库在内的任何连接完成,不允许建立任何新的/新的连接。由于您正在使用可能是您正在寻找的模块,但是快速的NPM搜索将显示适合Http服务器等的整个模块列表。

答案 4 :(得分:0)

正确处理操作系统信号:https://www.npmjs.com/package/daemonix

正常关闭Express:https://www.npmjs.com/package/@stringstack/express https://www.npmjs.com/package/@stringstack/core

这种工具组合将在关闭时停止新的连接,允许现有连接完成,然后最终退出。

答案 5 :(得分:0)

如果允许的话,还有一个更好的解决方案,就是使用server-destroy软件包来减少工作量。在内部,此软件包将在每次连接后正常终止,然后允许服务器被“销毁”。在这种情况下,我们确保最终结束Express应用程序(如果使用调用函数,则有可能再次启动它)。这对我使用电子的作品有效,并且可以潜在地移植到标准服务器上:

const express = require('express')
const { ipcMain } = require('electron')
const enableDestroy = require('server-destroy')
const port = process.env.PORT || 3000

export const wsServer = () => {
  try {
    let app = null
    let server = null

    const startServer = () => {
      if (app) {
        app = null
      }

      app = express()
      app.use(express.static('public'))
      app.use('/', (req, res) => {
        res.send('hello!')
      })

      server = app.listen(3000, () => {
        console.log('websocket server is ready.')
        console.log(`Running webserver on http://localhost:${port}`)
      })

      enableDestroy(server)
    }

    const stopServer = () => {
      if (server !== null) {
        server.destroy()
        app = null
        server = null
      }
    }
    const restartServer = () => {
      stopServer()
      startServer()
    }

    ipcMain.on('start-socket-service', (event) => {
      startServer()
      console.log('Start Server...')
      event.returnValue = 'Service Started'
    })

    ipcMain.on('stop-socket-service', (event) => {
      stopServer()
      console.log('Stop Server...')
      event.returnValue = 'Service Stopped'
    })

    ipcMain.on('restart-socket-service', () => {
      restartServer()
    })

  } catch (e) {
    console.log(e)
  }
}