ASP.NET Core SignalR (十一):托管和扩展
此为系列文章,对MSDN ASP.NET Core SignalR 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解。
这篇文章解释了对于使用ASP.NET Core SignalR 的高流量app的托管和缩放的注意事项。
粘性会话
SignalR 要求对于某一个特定连接的所有HTTP 请求都需要被一个相同服务进程处理。当SignalR 运行在一个服务场时(多个服务器),则必须使用“粘性会话”。“粘性会话”也被一些负载均衡器成为“会话类同”(session affinity)。Azure app 服务使用 Application Request Routing(ARR)来路由请求。在你的Azure app 服务中启用 ”ARR Affinity“设置就会启用”粘性会话“。仅有的不需要使用”粘性会话“的场景是:
- 当托管在一个单独的服务器上,在一个单独的进程中。
- 当使用Azure SignalR 服务时。
- 当所有的客户端被配置为仅使用WebSocket,并且在客户端配置中,启用了SkipNegotiation。
除了以上场景之外,在所有其他的场景下(包括当使用Redis底板时),服务端环境必须被配置为”粘性会话“。
关于为SignalR 配置 Azure App Service的指南,请参考Publish an ASP.NET Core SignalR app to Azure App Service。
TCP 连接资源
一个Web服务器所能够支持的并发TCP连接数是有限的。标准的HTTP客户端使用短连接。当客户端变得空闲时这些连接可以被关闭并且在稍后被重新打开。而在另一方面,SignalR连接是持久的。甚至当客户端变得空闲时,SignalR 连接依然是保持打开的。在一个服务于多个客户端的高流量app中,这些持久性的连接可以达到它们连接的最大值。
持久性连接也会消耗一些额外的资源来跟踪这些连接。
SignalR 对于连接相关的资源的大量使用同样也会影响托管在同一台服务器上的其他web app。当SignalR打开并持有最后一个可用的TCP连接的时候,同一台服务器上的其他app便没有更多可用的连接了。
如果一个服务器耗尽了连接资源,你会看到随机的sockets 错误以及连接重置错误。比如:
An attempt was made to access a socket in a way forbidden by its access permissions...
为了保持SignalR资源是可用的而不会在其他web app 中导致错误,在与你的web app 不同的服务器上运行SignalR。
为了保持SignalR资源是可用的而不会导致SignalR app产生错误,向外扩展以限制一个服务器需要处理的连接数。
横向扩展
使用SignalR 的 app 需要追踪其所有的连接,这对于一个服务场来说便产生了问题。当我们添加一个服务器时,其得到了一些新的连接,而其他服务器对此并不知情。举个例子,在如下的表格中,各个服务器上的SignalR 不会意识到其他服务器上的连接。当其中之一服务器上的SignalR想给所有的客户端发送消息时,消息只会发送给连接到这个服务器的客户端。
解决这个问题的选项是Azure SignalR Service 和 Redis backplane。
Azure SignalR 服务
Azure SignalR 服务是一个代理而不是一个底板(backplane)。每一次客户端初始化一个到服务器的连接,客户端都会被重定位连接到服务。这个过程在下图中被演示:
结果便是当每个服务器仅仅连接着少数的恒定数量的连接的时候,Azure SignalR 服务管理着所有的客户端连接,如同下列图标所示:
这个横向扩展的方案具比起Redis 替代方案来说,有几个优点:
- ”粘性会话“,也被称之为 client affinity,不再被需要。因为客户端在它们连接时被快速定位到Azure SignalR 服务。
- 一个SignalR app可以基于发送消息的数量进行横向扩展,而Azure SignslR 服务可以自动扩展以处理任意数量的连接。举个例子,可能会有数以千计的连接,但是如果每秒仅有几个消息被发送,SignalR app将不会横向扩展到多个服务器,而是会自己处理这些连接。
- signaler应用程序使用的连接资源不会比没有signaler的web应用程序多得多。
正是因为这些原因,我们为所有寄宿在云上的 ASP.NET Core SignalR app 推荐Azure SignalR 服务,包含App Service, VMs, 以及 容器。
更多信息,请参考Azure SignalR Service documentation。
Redis 底板
Redis是一个内存中的键值存储,其以发布/订阅模式来支持一个消息系统。SignalR Redis 底板使用 发布/订阅特性来转发消息给其他服务器。当客户端建立一个连接时,连接信息会被传递给底板。当一个服务器想要给所有的客户端发送信息时,它会将信息发送给底板。底板知道所有已连接的客户端以及它们所在的服务器。它通过它们各自的服务器发送消息给所有的客户端。这个过程就如同下列图表所演示的:
对于托管在自己架构上的app来说,Redis 底板是推荐的横向扩展实现。如果在你的数据中心和一个Azure数据中心之间有大量的潜在连接的话,对于具有低延迟或高吞吐量要求的本地应用程序,Azure signaler服务可能不是一个实用的选项。
上述提到的Azure SignalR 服务的优点对于Redis 底板来说是缺点:
- ”粘性会话“,也被称之为 client affinity,会被强制需要,除非以下两条都为真:
- 所有的客户端都被配置为仅使用WebSockets。
- 在客户端配置中,启用了SkipNegotiation setting。当一个连接在服务器被初始化时,连接必须保留在那个服务器上。
- SignalR app必须基于客户端的数量来进行横向扩展,即使有很少的消息正在被发送。
- 一个SignalR app 比一个没有使用SignalR 的app 使用了多得多的连接资源。
在Windows 客户端操作系统中IIS 的限制
Windows 10 及 Windows 8.x 是客户端操作系统。在客户端操作系统上的IIS 具有10个并发数的限制。SignalR 的 连接是:
- Transient 并经常需要重建。
- 当不再使用时不会被立即释放。
如上的情况使得在一个客户端操作系统上很可能达到一个10的上限。当一个客户端系统被用作开发时,我们建议:
- 避免使用IIS
- 使用Kestrel 或 IIS Express 作为开发目标。
使用Nginx 的Linux
使用代理的 Connection,并且对于SignalR WebSockets,将头信息更新为如下:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
更多信息,请参考NGINX as a WebSocket Proxy。
第三方 SignalR 底板提供器
下一步
更多信息,请参考以下资源: