如何构建基于Websockets的实时大量Web应用程序?


17

在开发实时单页应用程序的过程中,我逐渐采用了websocket,以向用户提供最新数据。在这个阶段,我很伤心地看到,我被摧毁了太多我的应用程序的结构,和我没能找到一个解决这种现象。

在详细介绍之前,请先介绍一下上下文:

  • 该webapp是实时SPA;
  • 后端在Ruby on Rails中。实时事件由Ruby推送到Redis密钥,然后微节点服务器将其拉回并推送到Socket.Io;
  • 前端位于AngularJS中,并直接连接到Node中的socket.io服务器。

在服务器端,在实时之前,我对资源进行了明确的基于控制器/模型的分离,每个过程都附加有处理。当我开始通过Websocket向用户推送内容时,这种经典的MVC设计就被完全粉碎,或者至少被绕开了。现在,我有一个管道,所有应用程序都将向下流动或多或少的结构化数据。而且我感到压力很大。

在前端,主要关注的是业务逻辑的重复。当用户加载页面时,我必须通过经典的AJAX调用加载模型。但是我还必须处理实时数据泛滥,而且我发现自己复制了很多客户端业务逻辑,以保持客户端模型的一致性。

经过一些研究,我找不到任何好的文章,文章,书籍或任何能给人们提供建议的关于如何可以并且应该如何设计现代Web应用程序体系结构的建议,并且要牢记一些特定的主题:

  • 如何构造从服务器发送给用户的数据?
    • 我应该只发送诸如“此资源已更新,您应该通过AJAX调用重新加载它”之类的事件,还是推送更新的数据并替换通过初始AJAX调用加载的先前数据?
    • 如何为发送的数据定义一个一致且可扩展的框架?这是模型更新消息还是“ blahblahblah出现错误”消息
  • 如何不从后端的任何地方发送有关所有内容的数据?
  • 如何减少服务器和客户端上的业务逻辑重复?

您确定Rails是您SPA的最佳选择吗?Rails很棒,但是它针对的是单块应用程序……您可能需要一个关注点分离的模块化后端……根据您的需求,我会考虑将其他Ruby框架用于实时SPA。
Myst

1
我不确定Rails是最好的选择,但是我对至少在后端的堆栈感到非常满意(可能是因为我对这个特定的框架很满意)。我的关注点更多是如何从技术不可知的角度设计SPA。而且我也不想在单个项目中增加语言,框架和库的数量。
菲利普·杜里克斯

链接的基准可能有问题,但确实暴露了ActiveRecord当前实施中的一个弱点。我可能对plezi.io抱有偏见,但正如基准测试的后续结果所指出的那样,即使在进行集群和Redis(未经测试)之前,它也提供了显着的性能改进。我认为它的性能要比node.js更好。
Myst

Answers:


10

如何构造从服务器发送给用户的数据?

使用消息传递模式。好了,您已经在使用消息传递协议,但是我的意思是将更改构造为消息……特别是事件。当服务器端发生更改时,将导致业务事件。在您的方案中,您的客户视图对这些事件感兴趣。事件应包含与该更改相关的所有数据(不一定是所有视图数据)。然后,客户端页面应使用事件数据更新其维护的视图部分。

例如,如果您要更新股票行情记录而AAPL发生了变化,则您不希望将所有股票价格甚至所有有关APL的数据(名称,描述等)下调。您只需按AAPL,价格增量和新价格即可。在客户端上,您将仅在视图上更新该股票价格。

我应该只发送诸如“此资源已更新,您应该通过AJAX调用重新加载它”之类的事件,还是推送更新的数据并替换通过初始AJAX调用加载的先前数据?

我不会说。如果要发送事件,请继续发送相关数据(而不是整个对象的数据)。为事件类型命名。(与该事件相关的命名和与之相关的数据超出了系统的机械工作范围。这与业务逻辑的建模方式有关。)您的视图更新者需要知道如何将每个特定事件转换为精确的视图更改(即仅更新更改的内容)。

如何为发送的数据定义一个一致且可扩展的框架?这是模型更新消息还是“ blahblahblah出现错误”消息

我会说这是一个开放的大问题,应该分解为其他几个问题并分别发布。

通常,后端系统应该为重要事件创建并调度事件。这些可能来自外部供稿,也可能来自后端本身的活动。

如何不从后端的任何地方发送有关所有内容的数据?

使用发布/订阅模式。当您的SPA加载一个有兴趣接收实时更新的新页面时,该页面应仅订阅它可以使用的那些事件,并在这些事件进入时调用视图更新逻辑。您可能需要使用pub / sub逻辑服务器以减少网络负载。Websocket发布/订阅库存在,但是我不确定Rails生态系统中的库。

如何减少服务器和客户端上的业务逻辑重复?

听起来您必须同时更新客户端和服务器上的视图数据。我的猜测是您需要服务器端视图数据,以便您具有快照来启动实时客户端。由于涉及到两种语言/平台(Ruby和Javascript),因此视图更新逻辑必须同时用两种语言/平台编写。除了转译(有其自身的问题)之外,我看不到解决办法。

技术要点:数据操作(视图更新)不是业务逻辑。如果您是指用例验证,那么这似乎是不可避免的,因为客户端的验证对于良好的用户体验是必需的,但最终不能被服务器信任。


这就是我如何看待这种结构良好的事情。

客户意见:

  • 请求一个视图快照和该视图的最近一次事件编号
    • 这将预填充视图,因此客户端不必从头开始构建。
    • 为了简单起见,可以通过HTTP GET
  • 从视图的最后一个事件编号开始,建立一个Websocket连接并预订特定事件。
  • 通过websocket接收事件并根据事件类型/数据更新其视图。

客户端命令:

  • 请求数据更改(HTTP PUT / POST / DELETE)
    • 响应只是成功或失败+错误
    • (更改产生的事件将通过websocket并触发视图更新。)

服务器端实际上可以分为责任有限的几个组件。一种只处理传入请求并创建事件的程序。另一个可以管理客户端订阅,侦听事件(例如进行中的事件)并将适当的事件转发给订阅者。您可能需要三分之一来侦听事件并更新服务器端视图-甚至可能在订阅者收到事件之前发生。

我所描述的是CQRS + Messaging的一种形式,也是解决您面临的这类问题的典型策略。

我没有在此描述中加入事件源,因为我不确定这是您要承担的任务还是必需的。但这是一个相关的模式。


我在该主题上取得了很大进步,您提供的指针非常有用。我接受了答案,因为即使没有使用全部建议,我也使用了许多建议。我将在另一个答案中描述我遵循的道路。
菲利普·杜里克斯

4

主要在后端工作了几个月后,我已经能够使用此处的一些建议来解决平台所面临的问题。

重新考虑后端的主要目的是尽可能地坚持CRUD。 分散在许多路由上的所有动作,消息和请求都被重新分组为创建,更新,读取或删除的资源。现在听起来很明显,但这是认真应用的很难思考的方法。

将所有内容整理成资源后,我便能够将实时消息附加到模型中。

  • 创建会触发带有新资源的消息;
  • 更新触发一条仅具有更新属性(加上UUID)的消息;
  • 删除会触发删除消息。

在Rest API上,所有的create,update,delete方法都生成一个head响应,HTTP代码通知成功或失败,以及实际数据通过websocket推送。

在前端,每​​个资源都由特定的组件处理,该组件在初始化时通过HTTP加载它们,然后订阅更新并随着时间的推移维护其状态。然后,视图绑定到这些组件以显示资源并通过相同的组件对这些资源执行操作。


我发现“ CQRS +消息传递和事件源”的读法非常有趣,但是感觉到它对于我的问题而言有点过于复杂,并且可能更适合于将数据提交到集中式数据库的昂贵奢侈品。但我一定会牢记这种方法。

在这种情况下,该应用程序将只有很少的并发客户端,而我则偏偏依赖数据库。变化最大的模型存储在Redis中,我相信它可以每秒处理数百次更新。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.