【问题标题】:Stop New Request to the Express server but can process older req and send response停止对 Express 服务器的新请求,但可以处理旧请求并发送响应
【发布时间】:2021-11-11 05:13:27
【问题描述】:

我正在处理正常关机。我的应用程序的 API 需要 10 -20 秒才能响应

server.close(
    () => {
      log('HTTP server closed')
    }
  );

上述代码在 API 调用之间存在时间间隔时有效,但它永远不会停止我的服务器,因为我的服务器在响应旧请求之前收到了新请求。

【问题讨论】:

    标签: node.js express graceful-shutdown


    【解决方案1】:

    节点.close() 方法几乎完全符合您的要求。具体来说:

    server.close([回调])
    添加于:v0.1.90
    callback 关闭服务器时调用。
    Returns:
    停止服务器接受新连接并保持现有连接。

    您可能遇到的问题是,有人通过使用 keepalive 的现有连接发送新请求。实际上并没有创建新的连接,但效果是一样的。

    我发现解决此问题的唯一方法是主动跟踪所有打开的连接以及所述连接上的每个请求,然后在关闭时跟踪:

    1. 强制关闭没有活动请求的连接connection.destroy()
    2. 拒绝在现有连接上收到的请求request.res.end()

    实际的逻辑看起来像这样,这里缺少逻辑,但这应该让你足够接近解决它。

    const connections = [];
    let shuttingDown = false;
    
    server.on('connection', (conn) => {
      let connectionId = 'some unique id here';
      
      conn.connectionId = connectionId;
      conn.requests = []; // so we can track open requests
      
      conn.on('close' => {
        delete connections[connectionId];
      });
      
      connections[connectionId] = conn;  
    });
    
    server.on('request', (req) => {
      // I don't actually know if the req.connection.connectionId will exist here
      // due to possible race conditions, or immutability of the connection object
      // if that is the case you may need to find another way to determine a unique
      // identifier based on existing connection fields
      let conn = connections[req.connection.connectionId];
      conn.requests.push(req);
      
      function requestComplete() {
        // if connection still exists
        // logic here for deleting request from connection.requests array
        // if shutting down, and connection.requests.length = 0; then connection.end()
      }
      
      req.res.on('finish', requestComplete);
      req.res.on('close', requestComplete);
      req.res.on('end', requestComplete);
      
      // If the server is already shutting down before this request is received
      // We do this after adding listeners for the request in case this is the only
      // request for the connection, so that our existing logic will auto-close the
      // socket instead of needing to duplicate it here as a special case
      if (shuttingDown) {
        req.res.statusCode = 503;
        return req.res.end();
      }
    });
    
    function shutdown() {
      shuttingDown = true;
      server.close(() => {
        console.log('closed');
      });
    }

    推荐升级:

    • 在关闭期间记录打开的连接及其请求(如果花费的时间超过合理时间)
    • 在预定时间后强制关闭请求和连接(可能需要记录哪些是被强制的,以便了解某些请求是否卡住)
    • 检查是否有任何 longpoll 请求,这些请求是不确定的,因此必须被强制终止(text/event-stream 标头或您知道的仅服务于该内容的路径)

    【讨论】:

    • 对于上述保持连接列表的方法,对于非常大的每秒请求数是否有效?
    • 我在生产环境中以每个工作人员每秒 150 个请求的速度运行了类似的操作,没有任何问题。没有明确地测试超过这个限制。
    【解决方案2】:

    您可以实施中间件,在您开始关闭过程后立即拒绝传入的连接。

    // set this flag to true when you want to start 
    //   immediately rejecting new connections
    let pendingShutdown = false;
    
    // first middleware
    app.use((req, res, next) => {
       if (pendingShutdown) {
           // immediately reject the connection
           res.sendStatus(503);
       } else {
           next();
       }
    });
    

    当正在处理的连接完成后,不再长时间运行连接,服务器应该在您执行此操作时找到一个自然退出点:

    pendingShutdown = true;
    server.close();
    

    NPM 上也有一些模块也提供了各种关闭算法。

    然后,为了防止任何长期卡住的连接阻止您的服务器关闭,您可以在现有连接上设置超时,或者只设置全局超时并在超时后执行process.exit()(强制关闭) .

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-13
      • 2020-11-02
      • 1970-01-01
      • 2020-06-15
      • 2018-04-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多