ASP.NET Core SignalR 裝載和調整
作者:Andrew Stanton-Nurse、Brady Gaster 和 Tom Dykstra
本文說明使用 ASP.NET Core SignalR 的高流量應用程式的裝載和調整考量。
黏性工作階段
SignalR 要求由同一個伺服器流程處理特定連線的所有 HTTP 要求。 在伺服器陣列 (多部伺服器) 上執行 SignalR 時,必須使用「黏性工作階段」。 「黏性工作階段」亦稱為工作階段親和性。 Azure App Service 會使用應用程式要求路由 (ARR) 來路由傳送要求。 在 Azure App 服務中啟用「工作階段親和性」(ARR Affinity) 設定,會啟用「黏性工作階段」。應用程式不需要黏性工作階段的唯一情況如下:
- 當在單一流程中裝載在單一伺服器時。
- 使用 Azure SignalR 服務時(為服務而不是應用程式啟用黏性工作階段)。
- 當所有用戶端都設定為 僅 使用 WebSockets、 與 時,
SkipNegotiation
設定 在用戶端設定中啟用。
在所有其他情況下 (包括使用 Redis 後擋板),必須針對黏性工作階段設定伺服器環境。
如需為 SignalR 設定 Azure App Service 的指引,請參閱將 ASP.NET Core SignalR 應用程式發佈到 Azure App Service。 如需為使用 Azure SignalR 服務的 Blazor 應用程式設定黏性工作階段的指引,請參閱裝載和部署 ASP.NET Core 伺服器端 Blazor 應用程式。
TCP 連線資源
網頁伺服器可支援的並行 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 服務可充當即時流量的 Proxy,並在應用程式跨多部伺服器擴增時兼作後擋板。 每次用戶端起始與伺服器的連線時,用戶端都會重新導向以連線到該服務。 下圖說明此流程:
結果是該服務會管理所有用戶端連線,而每部伺服器只需要與服務建立少量的固定數量連線,如下圖所示:
比起 Redis 後擋板替代方法,這個擴增方法有數個優點:
- 不需要黏性工作階段 (也稱為用戶端親和性),因為用戶端在連線時會立即重新導向至 Azure SignalR 服務。
- SignalR 應用程式可以根據傳送的訊息數目進行擴增,而 Azure SignalR 服務可調整以處理任意數目的連線。 例如,可能有數千個用戶端,但如果每秒只傳送幾則訊息,SignalR 應用程式不需要擴增至多部伺服器,就能自行處理連線。
- 比起不使用 SignalR 的 Web 應用程式,SignalR 應用程式使用的連線資源不會顯著更多。
基於這些原因,建議您針對裝載於 Azure 的所有 ASP.NET Core SignalR 應用程式 (包括 App Service、VM 和容器) 使用 Azure SignalR 服務。
如需詳細資訊,請參閱 Azure SignalR 服務文件。
Redis 後擋板
Redis 是記憶體內部索引鍵/值存放區,可支援具有發佈/訂閱模型的傳訊系統。 SignalR Redis 後擋板使用發佈/訂閱功能,將訊息轉送至其他伺服器。 當用戶端建立連線時,連線資訊會傳遞至後擋板。 當伺服器想要將訊息傳送給所有用戶端時,即會傳送至後擋板。 後擋板知道所有連線的用戶端,以及其所在的伺服器。 其透過各自的伺服器,將訊息傳送給所有用戶端。 此流程如下列圖表所示:
對於在您自己的基礎結構上裝載的應用程式,Redis 後擋板是建議的擴增方法。 如果您的資料中心與 Azure 資料中心之間有顯著的連線延遲,對於具有低延遲或高輸送量需求的內部部署應用程式而言,Azure SignalR 服務可能不是實用選項。
先前指出的 Azure SignalR 服務優點即是 Redis 後擋板的缺點:
- 需要黏性工作階段 (也稱為用戶端親和性),但滿足以下兩個條件時除外:
- 所有用戶端都設定為僅使用 WebSockets。
- 在用戶端設定中啟用 SkipNegotiation 設定。 在伺服器上起始連線後,連線必須保留在該伺服器上。
- 即使傳送的訊息很少,SignalR 應用程式也必須根據用戶端的數目擴增。
- 比起不使用 SignalR 的 Web 應用程式,SignalR 應用程式使用的連線資源顯著更多。
Windows 用戶端作業系統上的 IIS 限制
Windows 10 和 Windows 8.x 是用戶端作業系統。 用戶端作業系統上的 IIS 限制為 10 個並行連線。 SignalR 的連線為:
- 暫時性且經常重新建立。
- 不再使用時,不會立即處置。
上述條件可能會達到用戶端作業系統的 10 個連線限制。 當用戶端作業系統用於開發時,建議您:
- 避免使用 IIS。
- 使用 Kestrel 或 IIS Express 作為部署目標。
使用 Nginx 的 Linux
以下包含為 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 位址,使用 ip_hash
將連線路由傳送至伺服器:
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;
}
}
最後,將 server
區段中的 proxy_pass http://localhost:5000
變更為 proxy_pass http://backend
。
如需透過 Nginx 設定 WebSocket 的詳細資訊,請參閱 NGINX 作為 WebSocket Proxy。
如需負載平衡和黏性工作階段的詳細資訊,請參閱 NGINX 負載平衡。
如需 ASP.NET Core 搭配 Nginx 的詳細資訊,請參閱下列文章:
第三方 SignalR 後擋板提供者
下一步
如需詳細資訊,請參閱以下資源: