【问题标题】:What is the good way to graceful start a Node.js webserver app优雅启动 Node.js 网络服务器应用程序的好方法是什么
【发布时间】:2020-05-05 18:58:48
【问题描述】:

当我使用 express-generator 生成网络服务器时,我得到了这个文件夹结构:

  • bin/www
  • 观看次数/...
  • app.js
  • package.json
  • ...

bin/www 像这样打电话给app.js

var app = require('../app');
// ...
var server = http.createServer(app);
server.listen(port);

app.js 像这样创建应用程序:

var express = require('express')
var mongoose = require('mongoose')

mongoose.connect(process.env.DATABASE_URL).then(
    () => {
      debug('Database is connected')
    },
    err => {
      debug('An error has occured with the database connection')
      process.exit(1)
    }
  )

var app = express()

// Midllewares
app.use(/* some middleware 1 */)
app.use(/* some middleware 2 */)
app.use(/* some middleware 3 */)
app.use(/* some middleware ... */)

// Routes
app.get('/', function(req, res, next) {
  res.json({'message': 'Welcome to my website'})
})
app.get('/users', function(req, res, next) {
  Users.find({}).exec(function(err, users) {
    if (err) {
      res.json({'message': 'An error occured'})
      return
    }
    res.json('users': users)
  })
})
// ... others routes ...

module.exports = app

好的,这是 express-generator 的网络服务器样板。但是如果我想以好的方式启动我的应用程序,我必须在我的应用程序准备好时调用process.send('ready')。 (“就绪”表示所有服务都可以使用:数据库、redis、调度程序...)(当您的应用程序准备好时调用process.send('ready') 是了解您的网络服务器应用程序已准备好的最佳实践。可以使用此信号通过进程管理或其他系统)

问题是在bin/www 中,应用程序在没有建立数据库连接的情况下启动(调用server.listen())。换句话说,没有网络服务器应用准备好监听流量的保险。

我读到在bin/www 中启动服务器是一种最佳做法

上面的例子并不完整,我们可以认为我们有一个应用程序在接受请求之前必须启动多个服务(服务示例:redis、作业调度程序、数据库连接、ftp 连接到另一个服务器...)

我已经查看了一些流行和高级的 Node.js 应用样板:

但在调用server.listen(port) 之前,它们都没有处理应用程序的就绪状态,这使得网络服务器开始监听传入的请求。这让我很惊讶,我不明白为什么

在接受传入请求之前,我们必须等待具有多项服务的网络服务器应用程序的代码示例:

bin/www:

var app = require('../app');
// ...
var server = http.createServer(app);
server.listen(port);

app.js:

var express = require('express')
var mongoose = require('mongoose')

// **************
// Service 1 : database
mongoose.connect(process.env.DATABASE_URL).then(
  () => {
    debug('Database is connected')
  },
  err => {
    debug('An error has occured with the database connection')
    process.exit(1)
  }
)
// **************

// **************
// Service 2
// Simulate a service that take 10 seconds to initialized
var myWeatherService = null
setTimeout(function() {
  myWeatherService.getWeatherForTown = function(town, callback) {
    weather = 'sun'
    callback(null, weather)
  }
}, 10*1000)
// **************

// **************
// Other services...
// **************

var app = express()

// Midllewares
app.use(/* some middleware 1 */)
app.use(/* some middleware 2 */)
app.use(/* some middleware 3 */)
app.use(/* some middleware ... */)

// Routes
app.get('/', function(req, res, next) {
  res.json({'message': 'Welcome to my website'})
})
app.get('/users', function(req, res, next) {
  Users.find({}).exec(function(err, users) {
    if (err) {
      res.json({'message': 'An error occured'})
      return
    }
    res.json({'users': users})
  })
})
app.get('/getParisWeather', function(req, res, next) {
  Users.getWeatherForTown('Paris', function(err, weather) {
    if (err) {
      res.json({'message': 'An error occured'})
      return
    }
    res.json({'town': 'Paris', weatcher: weather})
  })
})
// ... others routes ...

module.exports = app

如果我启动我的应用程序,然后在 myWeatherService 初始化之前调用 localhost:port/getParisWeather,我会得到一个错误

我已经想到了一个解决方案:将每个服务声明移动到 bin/www 中,并让 app.js 中只包含与 express app 声明有关的代码:

bin/www:

var app = require('../app');
var mongoose = require('mongoose')
var server = null;

Promise.resolve()
.then(function () {
  return new Promise(function (resolve, reject) {
    // start service 1
    console.log('Service 1 is ready')
    resolve()
  })
})
.then(function () {
  return new Promise(function (resolve, reject) {
    // start service 2
    console.log('Service 2 is ready')
    resolve()
  })
})
.then(function () {
  return new Promise(function (resolve, reject) {
    // start other services...
    console.log('Others services is ready')
    resolve()
  })
})
.then(function () {
  return new Promise(function (resolve, reject) {
    server = http.createServer(app);
    server.listen(port);
    console.log('Server start listenning')
  })
})
.then(function () {
  next()
})
.catch(next)
.finally(function () {

})
.done()

app.js:

var express = require('express')
var app = express()

// Midllewares
app.use(/* some middleware 1 */)
app.use(/* some middleware 2 */)
app.use(/* some middleware 3 */)
app.use(/* some middleware ... */)

// Routes
app.get('/', function(req, res, next) {
  res.json({'message': 'Welcome to my website'})
})
app.get('/users', function(req, res, next) {
  Users.find({}).exec(function(err, users) {
    if (err) {
      res.json({'message': 'An error occured'})
      return
    }
    res.json({'users': users})
  })
})
app.get('/getParisWeather', function(req, res, next) {
  Users.getWeatherForTown('Paris', function(err, weather) {
    if (err) {
      res.json({'message': 'An error occured'})
      return
    }
    res.json({'town': 'Paris', weatcher: weather})
  })
})
// ... others routes ...

module.exports = app

但我知道将逻辑放在 bin/www 不是一个好习惯,它必须只包含服务器起始行...

所以,我的问题是,我们必须如何启动网络服务器应用程序才能尊重最佳实践 // 最佳实践是什么?

我知道我可以将所有内容放在一个文件中,并在该文件的末尾启动网络服务器,这不是我的问题。我要问的是如何以好的方式和最佳实践来做到这一点

【问题讨论】:

  • 如果你有一个'ready'事件,为什么你不能在所有服务都准备好的时候做process.emit('ready')并通过监听像process.on('ready', () => app.listen(port))这样的事件来启动服务器
  • 从应用程序发出“就绪”事件并从应用程序监听“就绪”事件以在应用程序中发生某些事情是一种好习惯吗?我认为让应用程序自己监听以启动某些东西(在我们的例子中启动 server.listen() )在我看来不是一个好习惯!?编辑:我没有想到的另一件事:“就绪”事件是告诉应用程序已经准备好监听请求。如果你做process.on(ready, app.listen(port)),在你发出“ready”的那一刻,应用程序还没有准备好监听请求
  • 查看答案

标签: node.js express


【解决方案1】:

答案实际上取决于您的生态系统是什么样的。如果您知道您的应用程序将使用的所有服务,那么您可以尝试在路由代码之前调用的 expressjs 中间件函数中检查它们。您可以使用一组承诺来跟踪服务准备情况,并使用布尔值来判断所有服务是否准备就绪。如果所有服务都准备好了,那么中间件函数可以调用next(),但如果没有,那么您可能会返回一个 HTML 页面,告诉用户该站点正在进行维护或尚未准备好,他们应该稍后再试。我可以看到您将所有这些承诺封装在一个中间件函数中,该函数管理它们是否准备好不弄乱您的 app.js 或 bin/www 文件。

更新: 如果你想阻止服务器在服务准备好之前进行监听,那么你需要在同一个进程中设置你自己的基础设施,或者使用类似 supervisord 的东西来管理进程。例如,您可以设置一个“启动”过程来检查服务。服务准备好后,您的启动进程可以分叉并启动节点服务器或创建运行该服务器的子进程。您的节点应用程序中不需要任何服务检查逻辑;假设是如果它是由另一个进程启动的,那么服务已经启动并运行。你可以引入像 supervisord 这样的高级进程管理系统,或者将其全部保存在 nodejs 中并使用 child_process 模块。这种方法有助于将“启动”代码与“运行/应用”代码分开。

【讨论】:

  • 您的解决方案将起作用,但目标是在所有服务准备好之前不开始侦听请求,并在所有服务准备好之前响应“网站处于维护模式”消息。目的是仅在所有服务都准备好时才开始侦听请求示例:如果工作容器的所有服务都未准备好,您不会想象 Facebook 或 Stackoverflow 会为您请求的页面提供“网站维护”消息。在它的所有服务都准备好之前,这个 pod 不会监听请求
【解决方案2】:

考虑一个简单的 express api,它在端口 3000 上返回“OK”。

const express = require('express');
const app = express();

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

app.listen(3000, () => {
    console.log("App listening on port 3000");
});

一旦启动应用程序,它就会准备好接受连接。现在让我们休眠 5 秒钟以假装数据库准备就绪,然后手动触发“就绪”事件。当事件被捕获时,我们开始监听连接。

const express = require('express');
const sleep = time => new Promise(resolve => setTimeout(resolve, time));

const app = express();

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

sleep(5000)
.then(() => {
    process.emit("ready");
});

process.on("ready", () => {
    app.listen(3000, () => {
        console.log("App listening on port 3000");
    });
});

通过转到http://localhost:3000 进行测试。 5 秒后您会看到“OK”。

在这里,您不必在process 对象本身上发出“就绪”事件。自定义 EventEmitter 对象也可以完成这项工作。 process 对象继承自 EventEmitter 并且全局可用。因此,这是收听任何全局事件的便捷方式。

【讨论】:

    猜你喜欢
    • 2012-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-10
    • 2015-08-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多