分享方式:


Azure SignalR Service 常見問題的疑難排解指南

本文針對客戶可能遇到的一些常見問題提供疑難排解指引。

存取權杖太長

可能的錯誤

  • 用戶端 ERR_CONNECTION_
  • 414 URI 太長
  • 413 檔案太大
  • 存取權杖不得超過 4K。 413 要求實體太大

根本原因

針對 HTTP/2,單一標頭的最大長度為 4 K,因此如果使用瀏覽器來存取 Azure 服務,則此限制會有錯誤 ERR_CONNECTION_

針對 HTTP/1.1 或 C# 用戶端,URI 長度上限為 12 K,標頭長度上限則為 16 K

針對 SDK 1.0.6 版或更新版本,在產生的存取權杖大於 4 K 時,/negotiate 將會擲回 413 Payload Too Large

解決方案

根據預設,在為 ASRS (Azure Signal RService) 產生 JWT 存取權杖時會包括來自 context.User.Claims 的宣告,以便在用戶端連接至 Hub 時保留宣告並可將宣告從 ASRS 傳遞至 Hub

在某些情況下,context.User.Claims 會用於儲存應用程式伺服器的大量資訊,其中大部分資訊是由其他元件使用,而非由 Hub 使用。

產生的存取權杖會透過網路傳遞,至於 WebSocket/SSE 連線,存取權杖會透過查詢字串傳遞。 因此,我們建議的最佳做法是在中樞需要時,僅將必要宣告透過 ASRS 從用戶端傳遞至應用程式伺服器。

ClaimsProvider 可供您自訂傳遞至存取權杖內 ASRS 的宣告。

ASP.NET Core:

services.AddSignalR()
        .AddAzureSignalR(options =>
            {
                // pick up necessary claims
                options.ClaimsProvider = context => context.User.Claims.Where(...);
            });

ASP.NET:

services.MapAzureSignalR(GetType().FullName, options =>
            {
                // pick up necessary claims
                options.ClaimsProvider = context.Authentication?.User.Claims.Where(...);
            });

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

需要 TLS 1.2

可能的錯誤

  • ASP.NET「無伺服器可用」錯誤 #279
  • ASP.NET「連線無法啟用,資料無法傳送至服務」錯誤 #324
  • 「向 https://<API endpoint> 提出 HTTP 要求時發生錯誤。 此錯誤可能源自於 HTTPS 情況下,伺服器憑證未使用 HTTP.SYS 正確設定。 此錯誤也可能因用戶端與伺服器之間的安全性繫結不相符所導致。」

根本原因

出於安全性考量,Azure 服務僅支援 TLS1.2。 使用 .NET Framework 時,TLS1.2 可能並非預設通訊協定。 因此,無法成功建立與 ASRS 的伺服器連線。

疑難排解指南

  1. 若能在本機重現此錯誤,請取消核取 Just My Code 並擲回所有 CLR 例外狀況,並在本機為應用程式伺服器偵錯,以查看擲回的例外狀況。

    • 取消核取 Just My Code

      Uncheck Just My Code

    • 擲回 CLR 例外狀況

      Throw CLR exceptions

    • 查看偵錯應用程式伺服器端程式碼時擲回的例外狀況:

      Exception throws

  2. 針對 ASP.NET 程式碼,您也可以將下列程式碼新增至 Startup.cs,以啟用詳細的追蹤,並查看記錄中的錯誤。

    app.MapAzureSignalR(this.GetType().FullName);
    // Make sure this switch is called after MapAzureSignalR
    GlobalHost.TraceManager.Switch.Level = SourceLevels.Information;
    

解決方案

將下列程式碼新增至 Startup:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

針對用戶端要求傳回 400 不正確的要求

根本原因

檢查您的用戶端要求是否具有多個 hub 查詢字串。 hub 是保留的查詢參數,如果服務在查詢中偵測到多個 hub,則會擲回 400。

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

針對用戶端要求傳回 401 未經授權

根本原因

目前,JWT 權杖存留期的預設值為一 (1) 小時。

針對 ASP.NET Core SignalR,使用 WebSocket 傳輸類型時不會有任何問題。

針對 ASP.NET Core SignalR 的其他傳輸類型 (SSE 和長時間輪詢),預設存留期則表示在預設情況下連線最多可以持續一小時。

針對 ASP.NET SignalR,用戶端會不時向服務傳送 /ping「保持運作」要求,當 /ping 失敗時,用戶端會中止連線,且永不重新連線。 針對 ASP.NET SignalR,預設權杖存留期會提供所有傳輸類型最多一小時的連線時間。

解決方案

基於安全性考量,不建議擴充 TTL。 我們建議從用戶端新增重新連線邏輯,以便於此類 401 發生時重新啟動連線。 當用戶端重新啟動連線時,會與應用程式伺服器交涉,從而再次取得 JWT 權杖,並取得更新的權杖。

請參閱此處了解如何重新啟動用戶端連線。

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

針對用戶端要求傳回 404

針對 SignalR 持續連線,它會先 /negotiate 至 Azure SignalR 服務,然後建立與 Azure SignalR 服務的實際連線。

疑難排解指南

  • 請遵循如何檢視連出要求,取得從用戶端到服務的要求。
  • 在發生 404 時,檢查要求的 URL。 如果 URL 以 Web 應用程式為目標,並且類似於 {your_web_app}/hubs/{hubName},請檢查用戶端 SkipNegotiation 是否為 true。 用戶端首次與應用程式伺服器交涉時,會收到重新導向 URL。 使用 Azure SignalR 時,用戶端不得略過交涉。
  • 在呼叫 /negotiate 之後,若連線要求處理超過五 (5) 秒時,就可能會發生另一個 404。 檢查用戶端要求的時間戳記,如果對服務的要求回應緩慢時,請向我們提出問題。

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

ASP.NET SignalR 的重新連線要求傳回了 404

針對 ASP.NET SignalR,當用戶端連線中斷時,系統會先使用相同的 connectionId 重新嘗試連線三次,接著才會停止連線。 若因網路間歇性問題導致連線中斷,/reconnect 可以協助 /reconnect 成功重新建立持續連線。 在其他情況下,例如,路由伺服器連線中斷導致用戶端連線因而中斷,或 SignalR Service 出現一些內部錯誤 (例如執行個體重新啟動/容錯移轉/部署),導致連線已不存在,因此 /reconnect 會傳回 404。 這是三次重試連線停止之後 /reconnect 的預期行為。 我們建議在連線停止時使用連線重新啟動邏輯。

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

針對用戶端要求傳回 429 (要求太多)

有兩種案例。

並行連線計數超過限制

針對免費執行個體,並行連線計數限制為 20。針對標準執行個體,每個單位並行連線計數限制為 1 K,這表示 Unit100 允許 100-K 的並行連線。

連線包括用戶端和伺服器連線。 請查看此處了解連線的計數方式。

同時提出太多交涉要求

我們建議在重新連線之前使用隨機延遲,請於此處查看重試範例。

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

交涉時發生 500 錯誤:Azure SignalR Service 尚未連線,請稍後再試一次

根本原因

當沒有與 Azure SignalR Service 連線的伺服器連線時,就會報告此錯誤。

疑難排解指南

啟用伺服器端追蹤,以在伺服器嘗試連線至 Azure SignalR Service 時找出錯誤詳細資料。

啟用 ASP.NET Core SignalR 的伺服器端記錄

ASP.NET Core SignalR 的伺服器端記錄會與 ASP.NET Core 架構中提供的 ILogger記錄整合。 您可以使用 ConfigureLogging 來啟用伺服器端記錄,範例使用方式如下:

.ConfigureLogging((hostingContext, logging) =>
        {
            logging.AddConsole();
            logging.AddDebug();
        })

Azure SignalR 的記錄器類別一律會以 Microsoft.Azure.SignalR 開頭。 若要從 Azure SignalR 啟用詳細的記錄,請將上述前置詞設定為 appsettings.json 檔案中的 Debug 層級,如下所示:

{
    "Logging": {
        "LogLevel": {
            ...
            "Microsoft.Azure.SignalR": "Debug",
            ...
        }
    }
}

啟用 ASP.NET SignalR 的伺服器端追蹤

使用 SDK 版本 >= 1.0.0 時,您可以將下列內容新增至 web.config 來啟用追蹤:(詳細資料)

<system.diagnostics>
    <sources>
      <source name="Microsoft.Azure.SignalR" switchName="SignalRSwitch">
        <listeners>
          <add name="ASRS" />
        </listeners>
      </source>
    </sources>
    <!-- Sets the trace verbosity level -->
    <switches>
      <add name="SignalRSwitch" value="Information" />
    </switches>
    <!-- Specifies the trace writer for output -->
    <sharedListeners>
      <add name="ASRS" type="System.Diagnostics.TextWriterTraceListener" initializeData="asrs.log.txt" />
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

用戶端連線中斷

當用戶端連線至 Azure SignalR 時,用戶端與 Azure SignalR 之間的持續連線有時可能出於不同原因而中斷。 本節說明導致此類連線中斷的數種可能性,並提供如何識別根本原因的相關指引。

用戶端所見的可能錯誤

  • The remote party closed the WebSocket connection without completing the close handshake
  • Service timeout. 30000.00ms elapsed without receiving a message from service.
  • {"type":7,"error":"Connection closed with an error."}
  • {"type":7,"error":"Internal server error."}

根本原因

在各種情況下,用戶端連線可能會中斷:

  • Hub 擲回具有連入要求的例外狀況時
  • 當用戶端路由至的伺服器連線中斷時,請參閱下節以取得伺服器連線中斷的詳細資料
  • 當用戶端與 SignalR Service 之間發生網路連線問題時
  • 當 SignalR Service 出現一些內部錯誤時,例如執行個體重新啟動、容錯移轉、部署等等

疑難排解指南

  1. 開啟應用程式伺服器端記錄,查看是否出現任何異常狀況
  2. 檢查應用程式伺服器端事件記錄檔,查看應用程式伺服器是否已重新啟動
  3. 向我們建立問題,提供時間範圍,並透過電子郵件提供資源名稱給我們

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

用戶端連線會持續增加

這可能是由於用戶端連線使用不當所導致。 如果有人忘記停止/處置 SignalR 用戶端,則連線會維持開啟狀態。

在 Azure 入口網站資源功能表的 [監視] 區段中,從 SignalR 計量中看到的可能錯誤

Azure SignalR 計量中的用戶端連線會在長時間內持續增加。

Client connection increasing constantly

根本原因

永遠不會呼叫 SignalR 用戶端連線的 DisposeAsync,連線會保持開啟狀態。

疑難排解指南

檢查 SignalR 用戶端是否永遠不會關閉。

解決方案

檢查您是否關閉連線。 在使用之後,手動呼叫 HubConnection.DisposeAsync() 停止連線。

例如:

var connection = new HubConnectionBuilder()
	.WithUrl(...)
	.Build();
try
{
	await connection.StartAsync();
	// Do your stuff
	await connection.StopAsync();
}
finally
{
	await connection.DisposeAsync();
}

常見的用戶端連線使用方式不當

Azure 函式範例

當有人在 Azure 函式方法中建立 SignalR 用戶端連線,而非使其成為函式類別中的靜態成員時,通常會發生此問題。 您可能希望只建立一個用戶端連線,但卻看到用戶端連線計數在計量中不斷增加。 只有在 Azure 函式或 Azure SignalR 服務重新啟動後,所有這些連線才會中斷。 此行為是因為 Azure 函式會針對每個要求建立一個用戶端連線,如果您未在函式方法中停止用戶端連線,用戶端便會保持與 Azure SignalR 服務的連線。

解決方案

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

伺服器連線中斷

當應用程式伺服器啟動時,Azure SDK 會在背景中開始啟動與遠端 Azure SignalR 的伺服器連線。 如 Azure SignalR Service 內部項目中所述,Azure SignalR 會將連入用戶端流量路由至這些伺服器連線。 伺服器連線中斷後,其中所有用戶端連線也會隨之關閉。

由於應用程式伺服器與 SignalR Service 之間的連線為持續連線,因此可能會遇到網路連線問題。 在伺服器 SDK 中,我們針對伺服器連線使用一律重新連線策略。 我們建議的最佳做法是,使用者將持續重新連線邏輯新增至具有隨機延遲時間的用戶端,以避免向伺服器同時提出大量要求。

Azure SignalR Service 會定期發佈新版本,有時會針對 Azure 範圍進行修補或升級,或者偶爾會中斷我們的相依服務。 這些事件可能會導致短暫的服務中斷,但只要用戶端具有中斷連線/重新連線機制,便可將影響降至最低,如同任何用戶端造成的中斷連線-重新連線一樣。

本節說明導致伺服器連線中斷的數種可能性,並提供如何識別根本原因的相關指引。

伺服器端所見的可能錯誤

  • [Error]Connection "..." to the service was dropped
  • The remote party closed the WebSocket connection without completing the close handshake
  • Service timeout. 30000.00ms elapsed without receiving a message from service.

根本原因

伺服器服務連線由 ASRS (Azure SignalRService) 關閉。

針對 Ping 逾時,可能是由於伺服器端的高 CPU 使用量或執行緒集區耗盡所導致。

針對 ASP.NET SignalR,在 SDK 1.6.0 中已修正已知問題。 將您的 SDK 升級至最新版本。

執行緒集區耗盡

若您的伺服器耗盡,表示沒有任何執行緒正在處理訊息。 所有執行緒都不會在特定方法中回應。

一般而言,此案例是由同步中非同步,或非同步方法中的 Task.Result/Task.Wait() 所導致。

請參閱 ASP.NET Core 效能最佳做法

深入了解執行緒集區耗盡

如何偵測執行緒集區耗盡

檢查執行緒計數。 如果當時沒有尖峰,請採取下列步驟:

  • 若您使用的是 Azure App Service,請檢查計量中的執行緒計數。 檢查 Max 彙總:

    Screenshot of the Max thread count pane in Azure App Service.

  • 若您使用的是 .NET Framework,則可以在伺服器 VM 的效能監視器中找到計量

  • 若您在容器中使用 .NET Core,請參閱收集容器中的診斷資訊

您也可以使用程式碼偵測執行緒集區耗盡:

public class ThreadPoolStarvationDetector : EventListener
{
    private const int EventIdForThreadPoolWorkerThreadAdjustmentAdjustment = 55;
    private const uint ReasonForStarvation = 6;

    private readonly ILogger<ThreadPoolStarvationDetector> _logger;

    public ThreadPoolStarvationDetector(ILogger<ThreadPoolStarvationDetector> logger)
    {
        _logger = logger;
    }

    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (eventSource.Name == "Microsoft-Windows-DotNETRuntime")
        {
            EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        // See: https://learn.microsoft.com/dotnet/framework/performance/thread-pool-etw-events#threadpoolworkerthreadadjustmentadjustment
        if (eventData.EventId == EventIdForThreadPoolWorkerThreadAdjustmentAdjustment &&
            eventData.Payload[2] as uint? == ReasonForStarvation)
        {
            _logger.LogWarning("Thread pool starvation detected!");
        }
    }
}

將其新增至您的服務:

service.AddSingleton<ThreadPoolStarvationDetector>();

然後,在伺服器因 Ping 逾時而中斷連線時,請檢查您的記錄。

如何找出執行緒集區耗盡的根本原因

若要找出執行緒集區耗盡的根本原因:

  • 傾印記憶體,然後分析呼叫堆疊。 如需詳細資訊,請參閱收集和分析記憶體傾印
  • 當偵測到執行緒集區耗盡時,請使用 clrmd 傾印記憶體。 然後,請記錄呼叫堆疊。

疑難排解指南

  1. 開啟應用程式伺服器端記錄,查看是否出現任何異常狀況。
  2. 檢查應用程式伺服器端事件記錄檔,查看應用程式伺服器是否已重新啟動。
  3. 建立問題。 提供時間範圍,並將資源名稱透過電子郵件傳送給我們。

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

提示

如何檢視來自用戶端的連出要求?

以 ASP.NET Core one 為例 (與 ASP.NET one 類似):

  • 從瀏覽器:以 Chrome 為例,您可以使用 F12 來開啟主控台視窗,並切換至 [網路] 索引標籤。您可能需要使用 F5 重新整理頁面,以從最初開始擷取網路。

    Chrome View Network

  • 從 C# 用戶端:

    您可以使用 Fiddler 檢視本機 Web 流量。 自 Fiddler 4.5 開始支援 WebSocket 流量。

    Fiddler View Network

如何重新啟動用戶端連線?

以下程式碼範例包含使用 ALWAYS RETRY 策略的重新啟動連線邏輯:

有關於疑難排解的問題或意見反應嗎? 讓我們知道。

下一步

您已在本指南中了解如何處理常見問題。 您也可以深入了解一般疑難排解方法。