作者: 阿什利·斯坦顿-护士、 布雷迪·加斯特和 汤姆·迪克斯特拉
本文说明使用 ASP.NET Core SignalR 的高流量应用的托管和缩放注意事项。
粘滞会话
SignalR 要求同一服务器进程处理特定连接的所有 HTTP 请求。 当 SignalR 在服务器场(多台服务器)上运行时,必须使用“粘滞会话”。 “粘滞会话”也称为会话亲和性。 Azure 应用服务使用 Microsoft 应用程序请求路由(ARR)路由请求。 在应用服务应用中启用“会话亲和性”(ARR 相关性)设置会启用粘滞会话。
有三种情况下,应用不需要粘滞会话:
- 在单个进程中的单个服务器上托管
- 使用 Azure SignalR 服务时(为服务而非应用启用粘滞会话)
- 所有客户端都配置为 仅 使用 WebSocket,并且客户端配置中启用了 SkipNegotiation
在所有其他情况下(包括使用 Redis 背板时),服务器环境都必须配置为支持粘滞会话。
有关为 SignalR 配置 Azure 应用服务的指导,请参阅将 ASP.NET Core SignalR 应用发布到 Azure 应用服务。 有关为使用 Azure SignalR 服务的 Blazor 应用配置粘滞会话的指导,请参阅托管和部署 ASP.NET Core 服务器端 Blazor 应用。
TCP 连接资源
Web 服务器可以支持的并发 TCP 连接数受到限制。 标准 HTTP 客户端使用临时连接。 这些连接可以在客户端进入空闲状态时关闭,并在以后重新打开。 另一方面,SignalR 连接是持久性的。 SignalR 连接即使在客户端进入空闲状态时也保持打开状态。 在为许多客户端提供服务的高流量应用中,这些持久性连接可能会导致服务器达到其最大连接数。
持久连接还会占用额外的内存,以跟踪每个连接。
SignalR 大量使用连接相关资源可能会影响在同一服务器上托管的其他 Web 应用。 SignalR 打开并保持最后一个可用 TCP 连接时,同一服务器上其他 Web 应用也不再有可用连接。
如果服务器用尽了连接数,则会看到随机套接字错误和连接重置错误。 例如:
An attempt was made to access a socket in a way forbidden by its access permissions...
若要防止 SignalR 资源使用在其他 Web 应用中导致错误,请在与其他 Web 应用不同的服务器上运行 SignalR。
若要防止 SignalR 资源使用在 SignalR 应用中导致错误,请横向扩展以限制服务器必须处理的连接数。
横向扩展
使用 SignalR 的应用需要跟踪其所有连接,这会给服务器场造成问题。 添加服务器,它会获取其他服务器不了解的新连接。 例如,下图中每个服务器上的 SignalR 都不了解其他服务器上的连接。 当其中一个服务器上的 SignalR 要向所有客户端发送消息时,消息只会发送给连接到该服务器的客户端。
解决此问题的选项包括 Azure SignalR 服务和 Redis 底板。
Azure SignalR 服务
Azure SignalR 服务用作实时流量的代理,会在应用跨多个服务器横向扩展时作为底板进行加倍。 每当客户端启动与服务器的连接时,客户端都会进行重定向以连接到服务。 下图演示了此过程:
结果是该服务会管理所有客户端连接,而每个服务器只需与该服务建立少量的恒定数量连接,如下图所示:
这种横向扩展方法相比 Redis 背板替代方案具有多项优势:
- 不需要粘滞会话(也称为 客户端关联性),因为客户端在连接时会立即被重定向到 Azure SignalR 服务。
- SignalR 应用可以基于发送的消息数进行横向扩展,而 Azure SignalR 服务可缩放以处理任意数量的连接。 例如,可能有数千个客户端,但如果每秒只发送几条消息,则 SignalR 应用不需要横向扩展到多个服务器,只需处理连接本身。
- 带有 SignalR 的应用相比不带有 SignalR 的 Web 应用,并不会占用多得多的连接资源。
出于这些原因,建议对Azure上托管的所有 ASP.NET Core SignalR 应用(包括应用服务、虚拟机和容器)使用 Azure SignalR 服务。
有关详细信息,请参阅 Azure SignalR 服务文档。
Redis 背板
Redis 是一种内存中键-值存储,支持具有发布/订阅模型的消息系统。 SignalR Redis 背板使用发布/订阅功能将消息转发到其他服务器。 当客户端建立连接时,连接信息会传递到底板。 当服务器想要向所有客户端发送消息时,它会将其发送到背板。 底板了解所有连接的客户端以及它们所处的服务器。 它通过各自的服务器将消息发送给所有客户端。 下图对此过程进行了阐释:
对于在你自己的基础结构上托管的应用,Redis 底板是推荐横向扩展方法。 如果您的数据中心与 Azure 数据中心之间存在明显的连接延迟,则 Azure SignalR 服务对于要求低延迟或高吞吐量的本地部署应用程序而言,可能不是切实可行的选择。
前面提到的 Azure SignalR 服务优势,对于 Redis backplane 来说却是缺点:
- 粘滞会话(也称为客户端相关性)是必需的,不过在满足以下两种条件时除外:
- 所有客户端都配置为仅使用 WebSocket。
- 在客户端配置中启用了 SkipNegotiation 设置。 在服务器上启动连接后,连接必须保留在该服务器上。
- 即使 SignalR 发送少量消息,应用也必须根据客户端数进行横向扩展。
- 应用 SignalR 使用比没有 SignalRWeb 应用的更多连接资源。
Windows客户端操作系统上的 IIS 限制
Windows 10 和 Windows 8.x 是客户端操作系统。 客户端操作系统上的 Internet Information Services (IIS) 限制为 10 个并发连接。 连接 SignalR 具有以下特征:
- 它们是暂时性的,经常重新建立。
- 它们在不再使用时 不会 立即被处置。
这些特征使得它可能会达到客户端操作系统上 10 个连接的限制。 使用客户端操作系统进行开发时,请考虑以下建议:
- 避免 IIS
- 使用 Kestrel 或 IIS Express 作为部署目标
Linux 与 Nginx
以下代码包含为 SignalR 启用 WebSockets、ServerSentEvents 和 LongPolling 所需的最低配置:
http {
map $http_connection $connection_upgrade {
"~*Upgrade" $http_connection;
default keep-alive;
}
server {
listen 80;
server_name example.com *.example.com;
# Configure the SignalR Endpoint
location /hubroute {
# App server url
proxy_pass http://localhost:5000;
# Configuration for WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache off;
# WebSockets were implemented after http/1.0
proxy_http_version 1.1;
# Configuration for ServerSentEvents
proxy_buffering off;
# Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds
proxy_read_timeout 100s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
当使用多个后端服务器时,必须启用粘滞会话,以防止 SignalR 连接在建立连接时切换到其他服务器。 可通过多种方法在 Nginx 中添加粘滞会话。 以下示例展示了两种方法,具体采用哪种方法取决于你现有的条件。
以下代码补充了前面的示例配置。 在代码片段中, backend 是服务器组的名称。
借助 Nginx 开源版,使用
ip_hash根据客户端 IP 地址将连接路由到服务器:http { upstream backend { # App server 1 server localhost:5000; # App server 2 server localhost:5002; ip_hash; } }使用 Nginx Plus,可使用
sticky为请求添加一个 cookie,并将用户请求固定到某台服务器上:http { upstream backend { # App server 1 server localhost:5000; # App server 2 server localhost:5002; sticky cookie srv_id expires=max domain=.example.com path=/ httponly; } }对于这两种配置,请将
proxy_pass http://localhost:5000部分中的server更改为proxy_pass http://backend。
你可以在 使用 Nginx 在 Linux 上托管 ASP.NET Core 中找到更多信息。
- 若要通过 Nginx 使用 WebSocket,请参阅 使用 Nginx 的 WebSocket 代理。
- 若要使用负载均衡和粘滞会话,请参阅 使用 Nginx 的 HTTP 负载均衡。
其他 SignalR 背板提供程序
以下非 Microsoft 提供商也提供 SignalR 背板: