【问题标题】:Is a node.js app that both servs a rest-api and handles web sockets a good idea?一个既能提供 rest-api 又能处理 web 套接字的 node.js 应用程序是个好主意吗?
【发布时间】:2021-10-14 16:27:23
【问题描述】:

免责声明:我是 node.js 的新手,所以如果这是一个奇怪的问题,我很抱歉 :)

我有一个使用 express.js 来提供 REST-API 的 node.js。 REST-API 提供的数据由 node.js 应用程序从 nosql 数据库中获取。所有客户端仅使用 HTTP-GET。但是有一个例外:从主数据库(另一台服务器上的关系数据库)中 PUT 和 DELETEd 数据。 这种设置的想法当然是让“node.js/nosql 数据库”服务器成为公共前端,从而保护主数据库免受大量流量的影响。

可能会有许多不同的客户端应用程序使用 REST-API,但主要是由具有较长生命周期(通常为 0.5 到 2 小时)的客户端应用程序使用。我不想让这个应用程序不断地轮询 REST-API 以获取可能的新数据,而是使用 websockets,以便仅在有任何新数据时才将数据发送到客户端。我将为此使用 node.js 应用程序,并且可能使用 socket.io,以便如果客户端不支持 websockets,它可以回退到 api-polling。每次 master 数据库在 nosql 数据库中 PUTs 或 DELETEs 对象时,都应该向客户端发送新数据。

问题是我是否应该为 API 和 websockets 使用一个 node.js,或者一个用于 API 和一个用于 websockets。

需要考虑的事项: - 性能:应用程序将托管在具有负载平衡器和 HTTP 加速器的服务器集群上。一个处理所有事情的应用程序会比两个具有不同任务的应用程序执行得更好吗? - 应用程序之间的流量:如果我选择两个应用程序解决方案,则从主数据库接收 PUT 和 DELETE 的 api 应用程序每次接收新数据时都必须注意 websocket 应用程序(或者主数据库必须注意两个应用程序)。翻倍的流量会是性能问题吗? - 代码清理:我相信两个应用程序会产生更干净和更好的代码,但是这两个应用程序肯定会有一些共同的代码,这将导致有两个副本。

很难说负载有多大,但可能出现的峰值可能涉及: 50000个客户 每个收听多达 5 个不同的频道 每 5 秒从主设备发送新数据 新数据应发送给大约 25% 的客户端(对于某些数据,它应该发送给所有客户端,而其他数据可能低于 1% 的客户端)

更新: 谢谢你们的答案。这里有更多的思想食物。我决定拥有两个 node.js 应用程序,一个用于 REST-API,一个用于 Web 套接字。原因是我相信扩展它们会更容易。首先,整个系统将托管在三台物理服务器上,每台服务器上的一个用于 REST-API 的 node.js 应用程序应该足够了,但对于 websocket 应用程序,每个物理服务器上可能需要多个实例。

【问题讨论】:

  • webSockets 在集群中的工作量要大得多,因为您必须有一种跨服务器的方式来了解哪个服务器与给定用户建立了连接,以便您可以将数据推送给该用户。您的 REST 数据库内容应该易于集群,因为没有持久的 node.js 服务器状态。因此,不集群 webSocket 服务器会更容易实现(如果 webSocket 规模可以由单个服务器处理)。

标签: node.js api rest websocket socket.io


【解决方案1】:

这是一个很好的问题。

如果您正在查看遗留系统,并且您已经定义了 REST 接口,那么添加 WebSocket 并没有太多优势。可能会将您指向 WebSockets 的事情是:

  • 对服务器到客户端或客户端到客户端实时数据的需求
  • 需要使用经典的双向协议与服务器组件集成(例如,您想用 javascript 编写 FTP 或 sendmail 客户端)。

如果您要开始一个新项目,我会尝试在以下项目中进行硬拆分:

  • 使用 HTTP 提供静态内容(图像、js、css)(这就是它的设计目的)和

  • 使用 WebSockets 提供动态内容(实时数据)(负载平衡、基于订阅/消息传递、启用自动重新连接以处理网络 blips)。

那么,我们为什么要尝试硬分离呢?让我们考虑一下基于 HTTP 的 REST 协议的优势。

将 HTTP 协议用于 REST 语义是一项具有一定优势的发明

  • 无状态交互:客户端的任何上下文都不会存储在请求之间的服务器端。
  • 可缓存:客户端可以缓存响应。
  • 分层系统:无法检测到中介
  • 轻松测试:使用 curl 轻松测试基于 HTTP 的协议

另一方面...

在 WebSocket 之上使用消息传递协议(例如 AMQP、JMS/STOMP)并不排除任何这些优势。

  • WebSockets 可以透明地进行负载平衡,可以缓存消息和状态,可以定义高效的有状态或无状态交互。

  • 基本的反应式分析风格可以定义哪些事件触发客户端和服务器之间的哪些消息。

主要的额外优势是:

  • WebSocket 旨在成为长期持久连接,可用于通过单个连接进行多种不同的消息传递

  • WebSocket 连接允许完全双向通信,允许根据网络特性在任一方向发送数据。

  • 可以使用连接卸载来通过中介共享对常见主题的订阅。这意味着只需很少的与核心消息代理的连接,您就可以大规模高效地为数百万连接用户提供服务。

  • 监控和测试可以通过管理界面来实现,以发送/接收消息(所有消息代理都提供)。

  • 这一切的代价是当 WebSocket 被丢弃后需要重新连接时,需要处理状态的重新建立。许多协议设计者构建了“同步”消息的概念,以提供从服务器到客户端的上下文。

无论您使用 REST 还是 WebSockets,您的模型对象可能都是相同的,但这可能意味着您仍然在请求-响应方面考虑太多,而不是在发布/订阅方面。

【讨论】:

  • 我最终做的是把它分开,但不是我的问题中计划的那样。我用 PHP 编写了 API,并有一个 node.js 应用程序处理 websockets。如果我也选择在 node.js 中编写 API,我现在建议不要只使用一个应用程序。原因是我的案例 websockets-app 基本上只是一个从后端向客户端发送消息的代理,而 API 具有更高级的功能。代码的 websockets 部分将“淹没”在所有 API 代码中。如果 ws 应用程序实际上正在对其传输的数据进行一些工作,那么我可能会重新考虑。
【解决方案2】:

您必须考虑的第一件事是如何扩展服务器并管理它们的状态。使用 REST API,这在很大程度上很简单,因为它们大部分是无状态的,并且每个负载均衡器都知道如何代理 http 请求。因此,REST API 可以水平扩展,将少量状态留给持久层(数据库)来处理。对于 websockets,通常情况不同。您需要研究要使用的负载均衡器(如果是云部署,通常取决于云提供商)。然后找出负载均衡器需要什么类型的 websocket 支持或配置。然后根据您的应用程序,您需要弄清楚如何管理跨集群的 websocket 连接的状态。考虑不同的用例,例如如果一台服务器上的 websocket 事件改变了数据的状态,您是否需要将此更改传播给不同连接上的不同用户?如果答案是肯定的,那么您可能需要 Redis 之类的东西来管理您的 ws 连接并在服务器之间传达更改。

至于性能,归根结底它仍然只是 HTTP 连接,所以我怀疑在分离服务器功能方面会有很大的不同。但是,我认为两台服务器将大大提高代码的清洁度,只要您有另一个“核心”模块来隔离两台服务器共有的代码。

【讨论】:

    【解决方案3】:

    我个人会一起做,这是因为您可以在 REST 和 WS 之间共享模型和大部分代码。

    归根结底,Yuri 在他的回答中所说的是正确的,但无论如何负载平衡 WS 并没有那么多工作,现在每个人都这样做。我采取的方法是让所有东西都使用 REST,然后创建一些 WS“端点”来订阅实时数据服务器客户端。

    因此,据我所知,您的客户端只会从服务器获取通知和更新,所以我肯定会选择 WS。您订阅了一些事件,然后在有新结果时获得。通过 HTTP 调用继续询问并不是最好的方法。

    我们有这个需求,基本上围绕这个想法构建了一个小框架http://devalien.github.io/Axolot/

    基本上,您可以理解我们在控制器中的方法(这只是一个示例,在我们的现实世界应用程序中,我们有订阅,因此我们可以在有新数据或完成程序时通知)。在操作中有其余端点,在套接字中有 websockets 端点。

    module.exports = {
        model: 'user', // We are attaching the user to the model, so CRUD operations are there (good for dev purposes)
        path: '/user', // Tthis is the end point
    
        actions: {
            'get /': [
                function (req, res) {
                    var query = {};
    
                    Model.user.find(query).then(function(user) { // Find from the User Model declared above
                        res.send(user);
                    }).catch(function (err){
                        res.send(400, err);
                    });
                }],
        },
        sockets: {
            getSingle: function(userId, cb) { // This one is callable from socket.io using "user:getSingle
                Model.user.findOne(userId).then(function(user) {
                    cb(user)
                }).catch(function (err){
                    cb({error: err})
                });
            }
        }
    };
    

    【讨论】:

      猜你喜欢
      • 2010-09-08
      • 1970-01-01
      • 2012-09-29
      • 2013-04-01
      • 2023-03-27
      • 2018-07-20
      • 1970-01-01
      • 2017-03-16
      • 1970-01-01
      相关资源
      最近更新 更多