託管和部署伺服器端 Blazor 應用程式

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱 本文的 ASP.NET Core 8.0 版本。

本文說明如何使用 ASP.NET Core 來託管和部署伺服器端 Blazor 應用程式。

主機組態值

伺服器端 Blazor 應用程式可以接受一般主機設定值

部署

Blazor 使用伺服器端託管模型,在伺服器上從 ASP.NET Core 應用程式中執行。 UI 更新、事件處理及 JavaScript 呼叫透過 SignalR 連線進行處理。

需要能夠裝載 ASP.NET Core 應用程式的網路伺服器。 Visual Studio 包含伺服器端應用程式專案範本。 如需 Blazor 專案範本的詳細資訊,請參閱 ASP.NET Core Blazor 專案結構

延展性

在考慮單一伺服器的可擴縮性 (擴大) 時,隨著使用者需求的增加,應用程式可用的記憶體可能是應用程式耗盡的第一個資源。 伺服器上的可用記憶體會影響:

  • 伺服器可支援的作用中線路數目。
  • 用戶端上的 UI 延遲。

如需建置安全且可調整伺服器端 Blazor 應用程式的指引,請參閱下列資源:

對於最小的 Hello World 樣式的應用程式,每個線路使用大約 250 KB 的記憶體。 線路的大小取決於應用程式的程式碼,以及與每個元件相關聯的狀態維護需求。 建議您在應用程式和基礎結構的開發期間測量資源的需求量,但以下基準可以作為規劃部署目標的起點:如果您預期應用程式支援 5,000 位並行使用者,請考慮為應用程式預定至少 1.3 GB 的伺服器記憶體 (或每位使用者約 273 KB)。

SignalR 設定

SignalR 的裝載和縮放情況適用於使用 SignalR 的 Blazor 應用程式。

傳輸

使用 WebSocket 作為 Blazor 傳輸時,因為延遲會較低、可靠性會更好且安全性會改善,因此 SignalR 會有最佳的運作情況。 當 WebSocket 無法使用或當應用程式明確設定為使用長輪詢時,SignalR 就會使用長輪詢。 在部署至 Azure App Service 時,請在服務的 Azure 入口網站設定中將應用程式設定為使用 WebSocket。 如需如何設定 Azure App Service 應用程式的詳細資訊,請參閱 SignalR 發行方針

如果使用長輪詢,就會出現主控台警告:

無法使用長輪詢後援傳輸來透過 WebSockets 連線。 這可能是因為 VPN 或 Proxy 封鎖該連線。

全域部署和連線失敗

全域部署至地理資料中心的建議:

  • 將應用程式部署至大部分使用者所在的區域。
  • 將跨洲時流量延遲會增加的情形納入考量。
  • 若為 Azure 裝載,請使用 Azure SignalR Service

如果已部署的應用程式因為網際網路延遲造成 Ping 逾時而經常顯示重新連線 UI,請延長伺服器和用戶端的逾時值:

  • Server

    至少將用戶端與伺服器之間的預期往返時間最大值加倍。 視需要測試、監視及修改逾時值。 針對 SignalR 中樞,設定 ClientTimeoutInterval (預設值:30 秒) 和 HandshakeTimeout (預設值:15 秒)。 下列範例假設 KeepAliveInterval 使用的預設值是 15 秒。

    重要

    KeepAliveInterval 不會與出現的重新連線 UI 直接相關。 Keep-Alive 間隔不一定需要變更。 如果會出現重新連線 UI 的問題是逾時造成的,則可以增加 ClientTimeoutIntervalHandshakeTimeout,而且 Keep-Alive 間隔可以維持不變。 重要的考量是,如果您變更 Keep-Alive 間隔,請確定用戶端的逾時值至少是 Keep-Alive 間隔值的兩倍,且用戶端上的 Keep-Alive 間隔要符合伺服器的設定。

    在下列範例中,ClientTimeoutInterval 會增加到 60 秒,HandshakeTimeout 會增加到 30 秒。

    在伺服器專案的 Program.cs 檔案中:

    builder.Services.AddRazorComponents().AddInteractiveServerComponents()
        .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
    });
    

    如需詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指引

  • Server

    至少將用戶端與伺服器之間的預期往返時間最大值加倍。 視需要測試、監視及修改逾時值。 針對 SignalR 中樞,設定 ClientTimeoutInterval (預設值:30 秒) 和 HandshakeTimeout (預設值:15 秒)。 下列範例假設 KeepAliveInterval 使用的預設值是 15 秒。

    重要

    KeepAliveInterval 不會與出現的重新連線 UI 直接相關。 Keep-Alive 間隔不一定需要變更。 如果會出現重新連線 UI 的問題是逾時造成的,則可以增加 ClientTimeoutIntervalHandshakeTimeout,而且 Keep-Alive 間隔可以維持不變。 重要的考量是,如果您變更 Keep-Alive 間隔,請確定用戶端的逾時值至少是 Keep-Alive 間隔值的兩倍,且用戶端上的 Keep-Alive 間隔要符合伺服器的設定。

    在下列範例中,ClientTimeoutInterval 會增加到 60 秒,HandshakeTimeout 會增加到 30 秒。

    Program.cs 中:

    builder.Services.AddServerSideBlazor()
        .AddHubOptions(options =>
        {
            options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
            options.HandshakeTimeout = TimeSpan.FromSeconds(30);
        });
    

    如需詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指引

  • 用戶端

一般而言,請將伺服器的 KeepAliveInterval 所使用的值加倍,以設定用戶端的伺服器逾時值 (withServerTimeoutServerTimeout,預設值:30 秒)。

重要

Keep-Alive 間隔 (withKeepAliveIntervalKeepAliveInterval) 不會與出現的重新連線 UI 直接相關。 Keep-Alive 間隔不一定需要變更。 如果會出現重新連線 UI 的問題是逾時造成的,則可以增加伺服器逾時值,而且 Keep-Alive 間隔可以維持不變。 重要的考量是,如果您變更 Keep-Alive 間隔,請確定逾時值至少是 Keep-Alive 間隔值的兩倍,且伺服器上的 Keep-Alive 間隔要符合用戶端的設定。

在下列範例中,伺服器逾時值會使用 60 秒的自訂值。

在伺服器端 Blazor 應用程式 Blazor 指令碼 (blazor.*.js) <script> 標籤之後的 啟動設定

Blazor Web 應用程式:

<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(60000);
      }
    }
  });
</script>

Blazor Server:

<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000);
    }
  });
</script>

在元件中建立中樞連線時,請在 HubConnectionBuilder 上設定 ServerTimeout (預設值:30 秒)。 在建置 HubConnection 上設定 HandshakeTimeout (預設值:15 秒)。

下列範例是以 SignalR 與 Blazor 教學課程中的 Index 元件為基礎。 伺服器逾時值會增加到 60 秒,交握逾時值則會增加到 30 秒:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(60))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

一般而言,請將伺服器的 KeepAliveInterval 所使用的值加倍,以設定用戶端的伺服器逾時值 (serverTimeoutInMillisecondsServerTimeout,預設值:30 秒)。

重要

Keep-Alive 間隔 (keepAliveIntervalInMillisecondsKeepAliveInterval) 不會與出現的重新連線 UI 直接相關。 Keep-Alive 間隔不一定需要變更。 如果會出現重新連線 UI 的問題是逾時造成的,則可以增加伺服器逾時值,而且 Keep-Alive 間隔可以維持不變。 重要的考量是,如果您變更 Keep-Alive 間隔,請確定逾時值至少是 Keep-Alive 間隔值的兩倍,且伺服器上的 Keep-Alive 間隔要符合用戶端的設定。

在下列範例中,伺服器逾時值會使用 60 秒的自訂值。

Pages/_Host.cshtml 中:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 60000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

在元件中建立中樞連線時,請在組建 HubConnection 上設定 ServerTimeout (預設值:30 秒) 和 HandshakeTimeout (預設值:15 秒)。

下列範例是以 SignalR 與 Blazor 教學課程中的 Index 元件為基礎。 伺服器逾時值會增加到 60 秒,交握逾時值則會增加到 30 秒:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

變更伺服器逾時值 (ServerTimeout) 或 Keep-Alive 間隔值 (KeepAliveInterval):

  • 伺服器逾時應至少是指派給 Keep-Alive 間隔的值的兩倍。
  • Keep-Alive 間隔應小於或等於指派給伺服器逾時值的一半。

如需詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指引

伺服器端應用程式會使用 ASP.NET Core SignalR 來與瀏覽器通訊。 SignalR 的託管和擴縮條件適用於伺服器端應用程式。

Blazor 在使用 WebSocket 作為 SignalR 傳輸協定時效果最佳,因為其延遲性較低且具備可靠性和安全性。 當 WebSocket 無法使用或當應用程式明確設定為使用長輪詢時,SignalR 就會使用長輪詢。 在部署至 Azure App Service 時,請在服務的 Azure 入口網站設定中將應用程式設定為使用 WebSocket。 如需設定適用於 Azure App Service 的應用程式的詳細資料,請參閱 SignalR 發佈指南

Azure SignalR Service

我們建議對伺服器端 Blazor 應用程式使用 Azure SignalR Service。 該服務可與應用程式的 Blazor 中樞搭配運作,以將伺服器端 Blazor 應用程式擴大為大量的並行 SignalR 連線數。 此外,SignalR 服務的全球觸達和高效能資料中心可大幅協助降低因地理位置造成的延遲。

重要

停用 WebSocket 時,Azure App Service 會使用 HTTP 長輪詢來模擬即時連線。 HTTP 長輪詢明顯比啟用 WebSocket 的情況 (不使用輪詢來模擬用戶端-伺服器連線) 執行的速度要慢。 如果必須使用長輪詢,則您可能需要設定最大的輪詢間隔 (MaxPollIntervalInSeconds),它定義了 Azure SignalR Service 中長輪詢連線允許的最大輪詢間隔 (如果該服務曾從 WebSocket 回退到長輪詢的話)。 如果在 MaxPollIntervalInSeconds 內未收到下一個輪詢要求,Azure SignalR Service 便會清除用戶端連線。 請注意,當快取等待寫入緩衝區大小大於 1 MB 時,Azure SignalR Service 也會清除連線以確保服務效能。 MaxPollIntervalInSeconds 的預設值為 5 秒。 此設定限制為 1-300 秒。

我們建議對部署至 Azure App Service 的伺服器端 Blazor 應用程式使用 WebSocket。 Azure SignalR Service 預設會使用 WebSocket。 如果應用程式不使用 Azure SignalR 服務,請參閱將 ASP.NET Core SignalR 應用程式發佈到 Azure App Service

如需詳細資訊,請參閱

組態

若要設定適用於 Azure SignalR Service 的應用程式,則應用程式必須支援黏性工作階段 (在當中用戶端會在預先呈現時被重新導向回到同一台伺服器)。 ServerStickyMode 選項或組態值會設定為 Required。 通常,應用程式會使用下列其中一種方法來建立組態:

  • Program.cs

    builder.Services.AddSignalR().AddAzureSignalR(options =>
    {
        options.ServerStickyMode = 
            Microsoft.Azure.SignalR.ServerStickyMode.Required;
    });
    
  • 組態 (使用下列其中一種方法):

    • appsettings.json 中:

      "Azure:SignalR:ServerStickyMode": "Required"
      
    • Azure 入口網站中應用程式服務的 [組態]>[應用程式設定] ([名稱]Azure__SignalR__ServerStickyMode[值]Required)。 如果您佈建 Azure SignalR Service,則應用程式會自動採用此方法。

注意

尚未對 Azure SignalR Service 啟用黏性工作階段的應用程式會擲回以下錯誤:

blazor.server.js:1 Uncaught (in promise) 錯誤:因基礎連線關閉而取消叫用。

佈建 Azure SignalR Service

若要在 Visual Studio 中為應用程式佈建 Azure SignalR Service:

  1. 在 Visual Studio 中為應用程式建立一個 Azure Apps 發佈設定檔。
  2. Azure SignalR Service 相依性新增至該設定檔中。 如果 Azure 訂用帳戶沒有預先存在的 Azure SignalR Service 實例來指派給應用程式,請選取 [建立新的 Azure SignalR 服務實例] 以佈建新的服務實例。
  3. 將應用程式發佈至 Azure。

在 Visual Studio 中佈建 Azure SignalR Service 會自動啟用 黏性工作階段,並將 SignalR 連接字串新增至應用程式服務的組態中。

Azure 容器應用程式的可擴縮性

除了使用 Azure SignalR Service 之外,在 Azure 容器應用程式上擴縮伺服器端 Blazor 應用程式還需要注意特定的事項。 由於處理要求路由傳送的方式,因此必須設定 ASP.NET Core 資料保護服務,以將金鑰保存在一個所有容器實例都可以存取的集中位置。 金鑰可以儲存在 Azure Blob 儲存體中,並使用 Azure Key Vault 進行保護。 資料保護服務會使用金鑰來還原序列化 Razor 元件。

注意

若要更深入地探索此案例和擴縮容器應用程式,請參閱在 Azure 上擴縮 ASP.NET Core 應用程式。 本教學課程說明如何建立和整合在 Azure 容器應用程式上託管應用程式所需的服務。 本節也提供了基本步驟。

  1. 若要將資料保護服務設定為使用 Azure Blob 儲存體和 Azure Key Vault,請參考下列 NuGet 套件:

    注意

    如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

  2. 使用下列醒目提示的程式碼更新 Program.cs

    using Azure.Identity;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
    var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];
    
    builder.Services.AddRazorPages();
    builder.Services.AddHttpClient();
    builder.Services.AddServerSideBlazor();
    
    builder.Services.AddAzureClientsCore();
    
    builder.Services.AddDataProtection()
                    .PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
                                                    new DefaultAzureCredential())
                    .ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
                                                    new DefaultAzureCredential());
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapRazorPages();
    
    app.Run();
    

    上面的變更可讓應用程式使用集中式、可調整的架構來管理資料保護。 DefaultAzureCredential 會在程式碼部署到 Azure 後探索到容器應用程式受控識別,並用它來連接到 Blob 儲存體和應用程式的金鑰保存庫。

  3. 若要建立容器應用程式受控識別,並授與它對 Blob 儲存體和金鑰保存庫的存取權,請完成下列步驟:

    1. 在 Azure 入口網站中,瀏覽至容器應用程式的概觀頁面。
    2. 從左側導覽中選取 [服務連接器]
    3. 從頂端導覽中選取 [+ 建立]
    4. [建立連線] 飛出視窗中,輸入下列值:
      • [容器]:選取您建立用於託管應用程式的容器應用程式。
      • [服務類型]:選取 [Blob 儲存體]
      • [訂用帳戶]:選取擁有容器應用程式的訂用帳戶。
      • [連線名稱]:輸入 scalablerazorstorage 的名稱。
      • [用戶端類型]:選取 [.NET],然後選取 [下一步]
    5. 選取 [系統指派的受控識別],然後選取 [下一步]
    6. 使用預設的網路設定,然後選取 [下一步]
    7. 在 Azure 驗證設定之後,選取 [建立]

    對金鑰保存庫重複上述設定。 在 [基本] 索引標籤中選取適當的金鑰保存庫服務和金鑰。

沒有 Azure SignalR Service 的 Azure App Service

使用 Azure SignalR Service 時,App Service 需要設定「應用程式要求路由」(ARR) 親和性和 WebSocket。 用戶端會將其 WebSocket 直接連接到應用程式 (而不是 Azure SignalR Service)。

使用下列指引來設定應用程式:

IIS

使用 IIS 時,請啟用:

Kubernetes

使用下列用於黏性工作階段的 Kubernetes 註釋建立輸入定義:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: <ingress-name>
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "affinity"
    nginx.ingress.kubernetes.io/session-cookie-expires: "14400"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "14400"

使用 Nginx 的 Linux

遵循 ASP.NET Core SignalR 應用程式的指導,並進行下列變更:

  • location 路徑從 /hubroute (location /hubroute { ... }) 變更為根路徑 / (location / { ... })。
  • 移除 Proxy 緩衝的組態 (proxy_buffering off;),因為此設定僅適用於伺服器傳送的事件 (SSE),而這與 Blazor 應用程式的用戶端伺服器互動無關。

如需詳細資訊和組態方面的指導,請參閱下列資源:

使用 Apache 的 Linux

若要在 Linux 上的 Apache 後面託管 Blazor 應用程式,請為 HTTP 和 WebSocket 流量設定 ProxyPass

在以下範例中:

  • Kestrel 伺服器在主機電腦上執行。
  • 應用程式接聽埠 5000 上的流量。
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

啟用下列模組:

a2enmod   proxy
a2enmod   proxy_wstunnel

檢查瀏覽器主控台是否有 WebSocket 錯誤。 範例錯誤:

  • Firefox 無法與位於 ws://the-domain-name.tld/_blazor?id=XXX 的伺服器建立連線
  • 錯誤:無法啟動傳輸 'WebSockets':錯誤:傳輸發生錯誤。
  • 錯誤:無法啟動傳輸 'LongPolling': TypeError: this.transport 未定義
  • 錯誤:無法使用任何可用的傳輸連接到伺服器。 WebSocket 失敗
  • 錯誤:如果連線未處於「已連線」狀態,則無法傳送資料。

如需詳細資訊和組態方面的指導,請參閱下列資源:

測量網路延遲

JS Interop 可用來測量網路延遲,如下列範例所示。

MeasureLatency.razor

@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}

為了獲得合理的 UI 體驗,我們建議持續的 UI 延遲為 250 毫秒或更短。

記憶體管理

在伺服器上,為每個使用者工作階段建立一個新線路。 每個使用者工作階段對應於在瀏覽器中呈現單一文件。 例如,多個索引標籤會建立多個工作階段。

Blazor 維護與起始工作階段的瀏覽器的持續連線 (稱為線路 (circuit))。 連線可能隨時會因多種原因而遺失 (例如當使用者遺失網路連線或突然關閉瀏覽器時)。 當連線遺失時,Blazor 會有一個復原機制,可將有限的線路數放入「已斷線」的集區中,並為用戶端提供一段有限的時間來重新連線並重新建立工作階段 (預設值:3 分鐘)。

在那段時間之後,Blazor 會釋放線路並捨棄工作階段。 從該點起,線路就符合進行記憶體回收 (GC) 的資格,且會在觸發線路的 GC 世代的回收時被回收。 需要了解的一個重要方面是線路具有一段很長的存留期 (這意味著線路所產生的大部分物件最終都會到達 Gen 2)。 因此,在進行 Gen 2 回收之前,您可能不會看到這些物件被釋放。

測量一般記憶體使用量

先決條件:

  • 應用程式必須在發行組態中發佈。 偵錯組態測量並不相關,因為產生的程式碼並不代表用於生產部署的程式碼。
  • 應用程式必須在未附加偵錯工具的情況下執行,因為這也可能會影響應用程式的行為並破壞結果。 在 Visual Studio 中,從功能表列選取 [偵錯>啟動但不偵錯] 或使用鍵盤按 Ctrl+F5,以啟動應用程式而不進行偵錯。
  • 思考不同的記憶體類型以了解 .NET 實際使用了多少記憶體。 通常,開發人員會在 Windows OS 上的 [作管理員] 中檢查應用程式的記憶體使用量 (這通常會提供實際使用記憶體的上限)。 如需詳細資訊,請參閱下列文章:

套用至 Blazor 的記憶體使用量

我們計算了 Blazor 所使用的記憶體,如下所示:

(作用中的線路數 × 每條線路的記憶體量) + (中斷連線的線路數 × 每條線路的記憶體量)

線路使用的記憶體量以及應用程式可以維護的最大可能的作用中線路數,主要取決於應用程式的撰編寫方式。 可能的使用中線路數目上限大致描述如下:

最大可用的記憶體量 / 每條線路的記憶體量 = 最大可能的作用中線路數

對於 Blazor 中發生記憶體洩漏,必須滿足以下條件:

  • 記憶體必須由架構配置,而不是應用程式。 如果您在應用程式中配置 1 GB 陣列,則應用程式必須管理該陣列的處置。
  • 記憶體不得被主動使用 (這意味著線路未處於作用中的狀態,而且已從中斷連線的線路快取中收回)。 如果您執行了最大的作用中線路數,則記憶體不足是一個縮放的問題,而不是記憶體洩漏的問題。
  • 線路 GC 世代的記憶體回收 (GC) 已經執行,但是記憶體回收行程無法回收線路,這是因為架構中的另一個物件持有對該線路的強烈引用。

在其他情況下,不存在記憶體洩漏的問題。 如果線路處於作用中的狀態 (已連線或已中斷連線),則該線路仍在使用中。

如果線路的 GC 世代回收未執行,則記憶體不會被釋放,因為記憶體回收行程當時不需要釋放記憶體。

如果 GC 世代的回收已執行並釋放線路,則您必須根據 GC 統計資料來驗證記憶體 (而不是處理程序),因為 .NET 可能會決定讓虛擬記憶體保持作用中。

如果記憶體未釋放,則您必須找到一條既不處於作用中也不是中斷連線,並且被架構中的另一個物件根引用的線路。 在任何其他情況下,無法釋放記憶體是開發人員程式碼中的應用程式問題。

減少記憶體使用量

採用下列任何策略來減少應用程式的記憶體使用量:

  • 限制 .NET 處理程序所使用的記憶體總量。 如需詳細資訊,請參閱記憶體回收的執行階段組態選項
  • 減少已中斷連線的線路數目。
  • 減少允許線路處於中斷連線狀態的時間。
  • 手動觸發記憶體回收,以在停機期間執行回收。
  • 在工作站模式 (而不是伺服器模式) 中設定記憶體回收 (這會主動觸發記憶體回收)。

其他動作

  • 在記憶體需求很高時擷取處理程序的記憶體傾印,並識別佔用最多記憶體的物件以及這些物件的根位置 (保存對它們的引用的物件)。
  • 伺服器模式下的 .NET 不會立即將記憶體釋放給作業系統,除非必須這樣做。 如需專案檔 (.csproj) 設定以控制此行為的詳細資訊,請參閱記憶體回收的執行階段組態選項
  • 伺服器 GC 會假設您的應用程式是唯一在系統上執行的應用程式,而且可以使用所有系統的資源。 如果系統有 50 GB,記憶體回收行程會在觸發 Gen 2 回收之前嘗試使用全部 50 GB 的可用記憶體。

如需中斷連線的線路保留組態的資訊,請參閱 ASP.NET Core BlazorSignalR 指引