使用 TCP 负载均衡器代理 WebSockets 而没有粘性会话

Posted

技术标签:

【中文标题】使用 TCP 负载均衡器代理 WebSockets 而没有粘性会话【英文标题】:Proxying WebSockets with TCP load balancer without sticky sessions 【发布时间】:2013-02-22 09:16:28 【问题描述】:

我想使用 Amazon Elastic Load Balancer 将 WebSocket 连接代理到多个 node.js 服务器。由于 Amazon ELB 不提供实际的 WebSocket 支持,因此我需要使用它的 vanilla TCP 消息传递。但是,我试图了解如果没有某种粘性会话功能,这将如何工作。

我了解 WebSockets 通过首先从客户端发送 HTTP 升级请求来工作,该请求由服务器通过发送正确处理密钥身份验证的响应来处理。在服务器发送该响应并被客户端批准后,该客户端和服务器之间就会建立双向连接。

但是,假设客户端在批准服务器响应后,将数据发送到服务器。如果它将数据发送到负载均衡器,然后负载均衡器将该数据中继到未处理原始 WebSocket 升级请求的不同服务器,那么这个新服务器将如何知道 WebSocket 连接?还是客户端会自动绕过负载均衡器,直接向处理初始升级的服务器发送数据?

【问题讨论】:

【参考方案1】:

我认为为了回答这个问题,我们需要了解的是,在整个 WebSocket 创建过程中,底层 TCP 连接究竟是如何演变的。您将意识到 WebSocket 连接的 sticky 部分是底层 TCP 连接本身。我不确定您在 WebSockets 上下文中的“会话”是什么意思。

在高层次上,启动“WebSocket 连接”需要客户端向 HTTP 服务器发送 HTTP GET 请求,而该请求包含 Upgrade 标头字段。现在,为了让这个请求发生,客户端需要建立到 HTTP 服务器的 TCP 连接(这可能很明显,但我认为在这里明确指出这一点很重要)。随后的 HTTP 服务器响应随后通过 same TCP 连接发送。

请注意,现在,在发送服务器响应后,如果客户端或服务器没有主动关闭,TCP 连接仍然是打开/活动的。

现在,根据 RFC 6455,WebSocket 标准,在section 4.1 的末尾:

如果服务器的响应按照上述规定进行验证,则它是 表示 WebSocket 连接已建立 并且 WebSocket 连接处于 OPEN 状态

我从这里读到,客户端在发送初始 HTTP GET(升级)请求之前启动的同一 TCP 连接将保持打开状态,从现在开始将用作全双工 WebSocket 连接的传输层.这是有道理的!

关于您的问题,这意味着负载均衡器只会在发出初始 HTTP GET(升级)请求之前发挥作用,即 之前只有在所述WebSocket连接创建中涉及的TCP连接是在两个通信端点之间建立的。此后,TCP 连接保持建立状态,不能被中间的网络设备“重定向”。

我们可以得出结论——在您的会话术语中——TCP 连接定义了会话。只要 WebSocket 连接处于活动状态(即未终止),它根据定义提供并存在于自己的会话中。没有什么可以改变这个会话。在这张图中,两个独立的 WebSocket 连接,但是,不能共享同一个会话。

如果您使用“会话”指代其他内容,那么它可能是应用层引入的会话,我们无法对此发表评论。

针对您的 cmets 进行编辑:

所以你说负载均衡器不参与 TCP 连接

不,这不是真的,至少在一般情况下是这样。它肯定会影响 TCP 连接的建立,从某种意义上说,它可以决定如何处理客户端连接尝试。具体取决于负载均衡器的确切类型(*,见下文)。重要提示:在两个端点之间建立连接后——虽然我不认为负载均衡器是一个端点,但我指的是 WebSocket 客户端和 WebSocket 服务器——这两个端点在 WebSocket 连接的生命周期内将不再改变.负载均衡器可能*仍在网络路径中,但可以假定不再受到影响。

因此全双工连接在客户端和客户端之间 终端服务器?

是的!

***有不同类型的负载平衡。根据类型的不同,负载均衡器在两个端点之间建立连接后的作用是不同的。例子:

如果负载平衡发生在 DNS 基础上,那么负载平衡器根本不参与最终的 TCP 连接。它只是告诉客户端必须直接连接到哪个主机。 如果负载均衡器像 AWS 的第 4 层 ELB(文档 here)一样工作,那么可以说代理 TCP 连接。因此,客户端实际上会将 ELB 本身视为服务器。然而,发生的情况是 ELB 只是在两个方向上转发包,而不做任何更改。因此,它仍然大量参与 TCP 连接,只是透明的。在这种情况下,实际上涉及两个永久 TCP 连接:一个从您到 ELB,一个从 ELB 到服务器。这些在您的 WebSocket 连接的整个生命周期内都是永久性的。

【讨论】:

所以你说负载均衡器不参与 TCP 连接,只有处理 UPGRADE 响应的终端服务器参与 TCP 连接。因此客户端和端服务器之间是全双工连接? 会话是指维护负载均衡器的状态,包括客户端的身份以及最初向哪个服务器发送 HTTP UPGRADE 请求。 但是在 ELB 的情况下,负载均衡器位于客户端和服务器端点之间,负载均衡器如何知道将来自同一客户端的后续数据发送到最初接收到的同一服务器HTTP 升级请求?这是我的主要问题,似乎还没有回答。 @JustinMeltzer:您可能想了解有状态和无状态协议之间的区别。虽然 HTTP 本身是一个无状态协议,但您的问题很有意义——通常独立的 HTTP 请求的负载平衡是一个挑战。在 HTTP 的情况下保持一个客户端和一个后端服务器之间的对应关系需要技巧。然而,TCP 连接是有状态的和永久的。根据定义,在 TCP 连接的情况下保持客户端和后端服务器之间的对应关系已经发生。如果违反了这个原则,就不再是 TCP 连接了。 ad 1) “有状态”和“无状态”是适用于许多情况的概念。你可以阅读很多关于它的内容。关于协议,你可以从这里开始:en.wikipedia.org/wiki/Stateless_protocol -- ad 2)ELB必须实现一个直接连接客户端ELB和ELB后端的机制。为简单起见,您可以将其视为查找表。【参考方案2】:

WebSocket 使用持久 TCP 连接,因此需要将该 TCP 连接的所有 IP 数据包转发到同一后端服务器(在 TCP 连接的生命周期内)。

它需要粘性。这与能够基于每个 HTTP 请求进行调度的 L7 HTTP LB 不同。

LB 可以通过不同的方法实现粘性,即

将源 IP/端口散列到一组活动的后端服务器 建立 TCP 连接后,选择后端服务器并记住这一点

【讨论】:

以上是关于使用 TCP 负载均衡器代理 WebSockets 而没有粘性会话的主要内容,如果未能解决你的问题,请参考以下文章

通过Nginx TCP反向代理实现Apache Doris负载均衡

Nginx支持TCP代理和负载均衡-stream模块

Nginx TCP代理和负载均衡

[转帖]Nginx 的 TCP 负载均衡介绍

Websocket 连接立即打开和关闭

Nginx stream 配置代理(Nginx TCP/UDP 负载均衡)