Поделиться через


Размещение и развертывание серверных Blazor приложений

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 9 этой статьи.

В этой статье объясняется, как размещать и развертывать серверные Blazor приложения (Blazor Web Apps и Blazor Server приложения) с помощью ASP.NET Core.

Значения конфигурации узла

Серверные приложения могут принимать значения конфигурации универсального Blazor узла.

Развертывание

Использование серверной модели Blazor размещения выполняется на сервере из приложения ASP.NET Core. Обновление элементов пользовательского интерфейса, обработка событий и вызовы JavaScript обрабатываются через подключение SignalR.

Необходим веб-сервер, позволяющий размещать приложения ASP.NET Core. Visual Studio включает шаблон проекта приложения на стороне сервера. Дополнительные сведения о шаблонах проектов Blazor см. в статье Структура проекта ASP.NET Core Blazor.

Опубликуйте приложение в конфигурации выпуска и разверните содержимое bin/Release/{TARGET FRAMEWORK}/publish папки, где {TARGET FRAMEWORK} заполнитель является целевой платформой.

Масштабируемость

При рассмотрении масштабируемости одного сервера (увеличение масштаба) объем памяти, доступной приложению, скорее всего, является первым ресурсом, который приложение исчерпает по мере увеличения требований пользователей. Объем доступной памяти на сервере влияет на:

  • число активных каналов, которые может поддерживать сервер;
  • задержку пользовательского интерфейса в клиенте.

Рекомендации по созданию безопасных и масштабируемых серверных Blazor приложений см. в следующих ресурсах:

Каждый канал использует около 250 КБ памяти для минималистичного приложения в стиле Hello World. Размер канала зависит от кода приложения и требований к обслуживанию состояний для каждого компонента. Мы рекомендуем измерять требования к ресурсам во время разработки приложения и инфраструктуры, но следующие базовые показатели могут быть отправной точкой при планировании целевого объекта развертывания. Если приложение будет поддерживать 5000 одновременных пользователей, рассмотрите возможность бюджетирования по крайней мере 1,3 ГБ памяти сервера приложению (или около 273 КБ на пользователя).

SignalRКонфигурация

SignalRУсловия размещения и масштабирования применяются к Blazor приложениям, которые используют SignalR.

Дополнительные сведения о приложениях, включая рекомендации по SignalR настройке, см. в Blazor ASP.NET основных BlazorSignalR руководствах.

Транспорты

В качестве транспортного механизма SignalR для Blazor лучше всего подходит WebSocket благодаря низкой задержке, хорошей надежности и улучшенной безопасности. SignalR использует продолжительный опрос, если это настроено в приложении явным образом или подключения WebSocket недоступны.

Появится предупреждение консоли, если используется длинный опрос:

Не удалось подключиться через WebSocket, используется резервный транспортный механизм длительного опроса. Это может быть связано с тем, что подключение блокируется каналом VPN или прокси-сервером.

Глобальные сбои развертывания и подключения

Рекомендации по глобальным развертываниям в географических центрах обработки данных:

  • Разверните приложение в регионах, где проживает большинство пользователей.
  • Учитывайте повышенную задержку трафика на разных континентах. Сведения о том, как управлять внешним видом пользовательского интерфейса повторного подключения, см. в руководстве ASP.NET CoreSignalRBlazor.
  • Рассмотрите возможность использования службы AzureSignalR.

Служба приложений Azure

Для размещения в службе приложение Azure требуется настройка для webSockets и сходства сеансов, также называемого сходством маршрутизации запросов приложений (ARR).

Примечание.

Приложению Blazor в службе приложение Azure не требуется служба AzureSignalR.

Включите следующую команду для регистрации приложения в службе приложение Azure:

  • WebSockets позволяет транспорту WebSockets выполнять функцию. Значение по умолчанию — Off.
  • Сходство сеансов для маршрутизации запросов от пользователя обратно в тот же экземпляр Служба приложений. Параметр по умолчанию включен.
  1. В портал Azure перейдите к веб-приложению в Служба приложений.
  2. Откройте конфигурацию параметров>.
  3. Установите для веб-сокетов значение "Вкл.".
  4. Убедитесь, что для параметра "Включено" задано сходство сеансов.

Служба Azure SignalR

Необязательная служба Azure SignalR работает вместе с центром приложения для масштабирования серверного приложения SignalR до большого количества одновременных подключений. Кроме того, глобальный охват службы и ее высокопроизводительные центры обработки данных обеспечивают низкую задержку благодаря доступности в разных регионах.

Служба не требуется для Blazor приложений, размещенных в службе приложение Azure или приложениях контейнеров Azure, но может быть полезно в других средах размещения:

  • Чтобы упростить горизонтальное масштабирование подключения.
  • Обработка глобального распределения.

Примечание.

Повторное подключение с отслеживанием состояния () было выпущено с помощью .NET 8,WithStatefulReconnect но в настоящее время не поддерживается для службы Azure SignalR . Дополнительные сведения см. в разделе "Поддержка повторного подключения с отслеживанием состояния"? (Azure/azure-signalr #1878).

Если приложение использует длинный опрос или возвращается к long Polling вместо WebSockets, возможно, потребуется настроить максимальный интервал опроса (MaxPollIntervalInSecondsпо умолчанию: 5 секунд, ограничение: 1–300 секунд), который определяет максимальный интервал опроса, разрешенный для подключений long Polling в службе Azure SignalR . Если следующий запрос опроса не поступает в пределах максимального интервала опроса, служба закрывает клиентское подключение.

Инструкции по добавлению службы в качестве зависимостей в рабочее развертывание см. в статье "Публикация приложения ASP.NET Core SignalR в службе приложение Azure".

Дополнительные сведения см. в разделе:

Приложения-контейнеры Azure

Более подробное изучение масштабируемых серверных Blazor приложений в службе "Приложения контейнеров Azure" см. в статье "Масштабирование ASP.NET основных приложений в Azure". В этом руководстве объясняется, как создавать и интегрировать службы, необходимые для размещения приложений в приложениях контейнеров Azure. Основные шаги также приведены в этом разделе.

  1. Настройте службу приложений контейнеров Azure для сопоставления сеансов, следуя инструкциям в руководстве по сопоставлению сеансов в приложениях контейнеров Azure (документация Azure).

  2. Служба ASP.NET Core Data Protection (DP) должна быть настроена для сохранения ключей в централизованном расположении, к которому могут получить доступ все экземпляры контейнеров. Ключи можно хранить в Хранилище BLOB-объектов Azure и защищать с помощью Azure Key Vault. Служба DP использует ключи для десериализации Razor компонентов. Чтобы настроить службу DP для использования Хранилище BLOB-объектов Azure и Azure Key Vault, см. следующие пакеты NuGet:

    • Azure.Identity: предоставляет классы для работы со службами управления доступом и Azure identity .
    • Microsoft.Extensions.Azure: предоставляет полезные методы расширения для выполнения основных конфигураций Azure.
    • Azure.Extensions.AspNetCore.DataProtection.Blobs: позволяет хранить ключи ASP.NET Core Data Protection в Хранилище BLOB-объектов Azure, чтобы ключи могли совместно использоваться в нескольких экземплярах веб-приложения.
    • Azure.Extensions.AspNetCore.DataProtection.Keys: включает защиту ключей при rest использовании функции шифрования и упаковки ключей Azure Key Vault.

    Примечание.

    Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.

  3. Обновите 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();
    

    Предыдущие изменения позволяют приложению управлять службой DP с помощью централизованной масштабируемой архитектуры. DefaultAzureCredential обнаруживает приложение-контейнер, управляемое identity после развертывания кода в Azure, и использует его для подключения к хранилищу BLOB-объектов и хранилищу ключей приложения.

  4. Чтобы создать управляемое identity приложение контейнера и предоставить ему доступ к хранилищу BLOB-объектов и хранилищу ключей, выполните следующие действия.

    1. На портале Azure перейдите на страницу обзора приложения-контейнера.
    2. Выберите соединитель службы в области навигации слева.
    3. Нажмите кнопку "+ Создать " в верхней области навигации.
    4. В меню "Создание подключения" введите следующие значения:
      • Контейнер. Выберите приложение-контейнер, созданное для размещения приложения.
      • Тип службы: выбор хранилища BLOB-объектов.
      • Подписка: выберите подписку, которая владеет приложением-контейнером.
      • Имя подключения: введите имя scalablerazorstorage.
      • Тип клиента: выберите .NET и нажмите кнопку "Далее".
    5. Выберите управляемое управление identity , назначенное системой, и нажмите кнопку "Далее".
    6. Используйте параметры сети по умолчанию и нажмите кнопку "Далее".
    7. После проверки параметров в Azure нажмите кнопку Создать.

    Повторите предыдущие параметры для хранилища ключей. Выберите соответствующую службу хранилища ключей и ключ на вкладке "Основные сведения".

IIS

При использовании IIS включите:

Дополнительные сведения см. в руководстве и внешних перекрестных ссылках ресурсов IIS в публикации приложения ASP.NET Core в 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"

Linux с Nginx

Следуйте указаниям для приложения ASP.NET Core SignalR со следующими изменениями:

  • Измените location путь с /hubroute (location /hubroute { ... }) на корневой путь / (location / { ... }).
  • Удалите конфигурацию для буферизации прокси-сервера (proxy_buffering off;), так как этот параметр применяется только к событиям, отправленным сервером (SSE), которые не относятся к взаимодействию между клиентом и сервером в приложении Blazor.

Дополнительные сведения и инструкции по настройке см. в следующих ресурсах:

Linux c Apache

Чтобы разместить приложение Blazor на основе Apache в Linux, настройте ProxyPass для трафика HTTP и WebSockets.

В следующем примере :

  • Сервер 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

Проверьте консоль браузера на наличие ошибок WebSockets. Примеры ошибок приведены далее.

  • Firefox не может установить соединение с сервером по адресу ws://the-domain-name.tld/_blazor?id=XXX
  • Ошибка: не удалось запустить транспорт WebSockets: ошибка: произошла ошибка с транспортом.
  • Ошибка: не удалось запустить транспорт LongPolling: TypeError: this.transport не определен
  • Ошибка. Не удается подключиться к серверу с любым из доступных транспортов. Сбой WebSockets.
  • Ошибка. Не удается отправить данные, если подключение не находится в состоянии "Подключено".

Дополнительные сведения и инструкции по настройке см. в следующих ресурсах:

Измерение задержки сети

средства взаимодействия JS можно использовать для оценки задержки сети, как показано в следующем примере:

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

<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();
        }
    }
}

Для удобной работы с пользовательским интерфейсом мы рекомендуем обеспечить стабильную задержку не более 250 мс.

Управление памятью

На сервере создается новый канал для каждого сеанса пользователя. Каждый сеанс пользователя соответствует отрисовке одного документа в браузере. Например, несколько вкладок создают несколько сеансов.

Blazorподдерживает постоянное подключение к браузеру, называемому каналом, который инициировал сеанс. Подключения могут быть потеряны в любое время по нескольким причинам, например, когда пользователь теряет сетевое подключение или резко закрывает браузер. Если подключение потеряно, имеет механизм восстановления, который помещает ограниченное количество каналов в пул "отключен", Blazor предоставляя клиентам ограниченное время для повторного подключения и повторного создания сеанса (по умолчанию: 3 минуты).

После этого освобождает Blazor канал и удаляет сеанс. С этого момента канал имеет право на сборку мусора (GC) и утверждается при активации коллекции для создания GC канала. Одним из важных аспектов является то, что каналы имеют длительное время существования, что означает, что большинство объектов, корневых цепи в конечном итоге достигли 2-го поколения. В результате эти объекты могут не отображаться до тех пор, пока не произойдет коллекция 2-го поколения.

Измерение использования памяти в целом

Необходимые условия:

  • Приложение должно быть опубликовано в конфигурации выпуска . Измерения конфигурации отладки не актуальны, так как созданный код не является представителем кода, используемого для рабочего развертывания.
  • Приложение должно работать без присоединенного отладчика, так как это может также повлиять на поведение приложения и испортить результаты. В Visual Studio запустите приложение без отладки, выбрав "Начать отладку>без отладки" в строке меню или CTRL+F5 с помощью клавиатуры.
  • Рассмотрим различные типы памяти, чтобы понять, сколько памяти фактически используется .NET. Как правило, разработчики проверяют использование памяти приложения в диспетчере задач в ОС Windows, что обычно предлагает верхнюю границу фактической памяти, используемой. Дополнительные сведения см. в следующих статьях:

Использование памяти, применяемое к Blazor

Мы вычисляем память, используемую blazor следующим образом:

(Активные каналы × памяти для каждого канала) + (отключенные каналы × памяти для каждого канала)

Объем памяти, используемой каналом, и максимальное количество активных каналов, которые может поддерживать приложение, в значительной степени зависит от того, как записывается приложение. Максимальное количество возможных активных каналов описывается примерно следующим образом:

Максимальный объем доступной памяти / = для каждого канала— максимально возможные активные каналы

Для утечки памяти в Blazorней должно быть указано следующее:

  • Память должна быть выделена платформой, а не приложением. Если вы выделяете массив размером 1 ГБ в приложении, приложение должно управлять удалением массива.
  • Память не должна использоваться активно, что означает, что канал не активен и был исключен из кэша отключенных каналов. Если у вас есть максимальное количество активных каналов, выполнение памяти является проблемой масштабирования, а не утечкой памяти.
  • Сборка мусора (GC) для создания GC канала запущена, но сборщик мусора не смог претендовать на канал, так как другой объект в платформе содержит сильную ссылку на канал.

В других случаях утечка памяти отсутствует. Если канал активен (подключен или отключен), канал по-прежнему используется.

Если коллекция для создания GC канала не выполняется, память не освобождается, так как сборщик мусора не должен освободить память в то время.

Если коллекция для создания GC запускается и освобождает канал, необходимо проверить память на основе статистики GC, а не процесса, так как .NET может решить сохранить виртуальную память активной.

Если память не освобождена, необходимо найти канал, который не является активным или отключенным, и он коренируется другим объектом в платформе. В любом случае неспособность освободить память является проблемой приложения в коде разработчика.

Сокращение использования памяти

Выполните любую из следующих стратегий, чтобы сократить использование памяти приложения:

  • Ограничить общий объем памяти, используемой процессом .NET. Дополнительные сведения см. в разделе "Параметры конфигурации среды выполнения" для сборки мусора.
  • Уменьшите количество отключенных каналов.
  • Уменьшите время, когда канал может находиться в отключенном состоянии.
  • Активация сборки мусора вручную для выполнения коллекции во время простоя.
  • Настройте сборку мусора в режиме рабочей станции, которая агрессивно активирует сборку мусора вместо режима сервера.

Размер кучи для некоторых браузеров мобильных устройств

При создании приложения, работающего Blazor на клиенте и предназначенных для браузеров мобильных устройств, особенно Safari в iOS, может потребоваться максимальное количество памяти для приложения со свойством EmccMaximumHeapSize MSBuild. Дополнительные сведения см. в статье Размещение и развертывание ASP.NET Core Blazor WebAssembly.

Дополнительные действия и рекомендации

  • Захват дампа памяти процесса, когда требования к памяти высоки и определяют, что объекты занимают большую память и где эти объекты корень (что содержит ссылку на них).
  • Вы можете просмотреть статистику о том, как работает dotnet-countersпамять в приложении. Дополнительные сведения см. в разделе "Изучение счетчиков производительности" (dotnet-counters).
  • Даже если GC активируется, .NET сохраняет память вместо того, чтобы немедленно вернуть ее в ОС, так как, скорее всего, она будет повторно использовать память в ближайшем будущем. Это позволяет избежать постоянного фиксации и вывода памяти, что является дорогостоящим. Вы увидите это, если вы используете dotnet-counters , так как вы увидите, что контроллеры GCs происходят, и объем используемой памяти уменьшается до 0 (ноль), но вы не увидите снижение счетчика рабочего набора, что является признаком того, что .NET держится на памяти для повторного использования. Дополнительные сведения о параметрах файла проекта (.csproj) для управления этим поведением см. в параметрах конфигурации среды выполнения для сборки мусора.
  • Серверная сборка мусора не запускает сборки мусора, пока она не определит, что это абсолютно необходимо, чтобы избежать замораживания приложения и считает, что ваше приложение является единственной вещью, работающей на компьютере, поэтому она может использовать всю память в системе. Если в системе имеется 50 ГБ, сборщик мусора стремится использовать полную 50 ГБ доступной памяти, прежде чем активировать коллекцию 2-го поколения.
  • Сведения о конфигурации хранения отключенных каналов см. в руководстве по ASP.NET CoreBlazorSignalR.

Измерение памяти

  • Опубликуйте приложение в конфигурации выпуска.
  • Запустите опубликованную версию приложения.
  • Не присоединяйте отладчик к работающему приложению.
  • Активирует ли принудительное сжатие коллекции 2-го поколения (GC.Collect(2, GCCollectionMode.Aggressive | GCCollectionMode.Forced, blocking: true, compacting: true)) освобождает память?
  • Рассмотрите возможность выделения объектов в куче больших объектов.
  • Вы тестируете рост памяти после того, как приложение нагревается с помощью запросов и обработки? Как правило, кэши заполняются при первом выполнении кода, добавляющего постоянный объем памяти в место приложения.