假设我有一个典型的Web服务器为客户端提供标准HTML页面,并且与它一起运行的websocket服务器用于实时更新(聊天,通知等) .
我的一般工作流程是当主服务器上发生触发实时消息需求的事件时,主服务器将该消息发送到实时服务器(通过消息队列),实时服务器将其分发给任何相关连接 .
我担心的是,如果我想扩展一点,并添加另一个实时服务器,似乎我唯一的选择是:
-
让主服务器跟踪客户端连接到哪个实时服务器 . 当该客户端收到通知/聊天消息时,主服务器将该消息转发到仅客户端连接的实时服务器 . 这里的缺点是代码复杂性,因为主服务器必须做一些额外的簿记 .
-
或者让主服务器简单地将该消息传递给每个实时服务器;只有客户端连接的服务器实际上会对它做任何事情 . 这将导致传递大量浪费的消息 .
我在这里错过了另一种选择吗?我只是想确保我不要走太远这些路径,并意识到我做错了 .
4 回答
如果方案是
a)主Web服务器在动作时引发消息(假设插入记录)b)他通知相应的实时服务器
您可以通过使用中间pub / sub体系结构将这两个步骤解耦,以便将消息转发给入口的收件人 .
实施将是
1)你有一个redis pub-sub通道,当客户端连接到实时套接字时,你开始在该通道中监听
2)当主应用程序想要通过实时服务器通知用户时,它会向通道发送消息,实时服务器获取它并将其转发给目标用户 .
这样,您可以将实时通知与主应用程序分离,而无需跟踪用户的位置 .
您所描述的问题是SignalR中常见的“消息背板”,也与消息体系结构中的“扇出消息交换”有关 . 有背板或进行扇出时,每条消息都会转发到每个消息节点服务器,因此客户端可以连接到任何服务器并获取消息 . 当您必须同时支持长轮询和websockets时,这种方法是一种合理的痛苦 . 但是,正如您所注意到的那样,这是对流量和资源的浪费 .
您需要使用具有智能路由的消息基础结构,如RabbitMQ . 看看主题和 Headers 交换:https://www.rabbitmq.com/tutorials/amqp-concepts.html
How Topic Exchanges Route Messages
RabbitMQ for Windows: Exchange Types
有许多不同的排队框架 . 选择你喜欢的那个,但确保你可以有更多的交换模式,而不仅仅是直接或扇出;)最后,WebSocket只是和 endpoints 连接到消息基础设施 . 所以,如果你想扩展,它可以归结为你拥有的后端:)
对于一些实时服务器,您可以想象只是在主服务器中保留它们的列表,然后循环遍历它们 .
另一种方法是使用load balancer .
基本上,您将有一个专用节点来接收来自主服务器的请求,然后让该负载 balancer 器节点负责选择将请求转发到哪个websocket / realtime服务器 .
当然,这只是将代码复杂性从主服务器转移到新组件,但从概念上讲,我认为它更好,更分离 .
更改了答案,因为答复表明“主”和“实时”服务器是负载均衡的集群,而不是单个主机 .
中心可扩展性问题似乎是:
强调“相关”一词 . 假设您有10个“主”服务器和50个“实时”服务器,主服务器#5上会发生一个事件:哪个websockets被认为与此事件有关?
最糟糕的情况是任何“主”服务器上的任何事件都需要传播到所有websockets . 这是O(N ^ 2)的复杂性,其被视为严重的可扩展性损害 .
这种O(N ^ 2)复杂度只能是如果您可以在不以群集大小或总nr增长的组中对相关连接进行分组,则会阻止 . 连接 . 分组需要状态存储器来存储连接所属的组 .
请记住,存储状态的方法有3种:
全局内存(memcached / redis / DB,...)
粘性路由(负载均衡器配置)
客户端内存(cookie,浏览器本地存储,链接/重定向URL)
选项3算作最具扩展性的选项,因为它省略了中央状态存储 .
为了将消息从“主”服务器传递到“实时”服务器,根据定义,该流量应远小于客户端的流量 . 还有有效的框架来推动发布/子流量 .