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

Примечание.

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

Внимание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Транспорты

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

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

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

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

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

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

Служба Azure SignalR

Для Blazor веб-приложения, которые применяют интерактивную отрисовку на стороне сервера, рассмотрите возможность использования службы AzureSignalR. Служба работает вместе с Центром приложения Blazor для масштабирования до большого количества одновременных SignalR подключений. Кроме того, глобальный охват службы и ее высокопроизводительные центры обработки данных обеспечивают низкую задержку благодаря доступности в разных регионах. Если среда размещения уже обрабатывает эти проблемы, использование службы Azure SignalR не требуется.

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

Внимание

Когда соединения WebSocket отключены, Служба приложений Azure имитирует соединение в реальном времени с помощью длинного опроса HTTP. Длинный опрос HTTP заметно медленнее, чем выполнение с включенными соединениями WebSockets, при котором не используются опросы для имитации соединения "клиент-сервер". В случае использования long Polling может потребоваться настроить максимальный интервал опроса (MaxPollIntervalInSeconds), который определяет максимальный интервал опроса, разрешенный для подключений long polling в службе AzureSignalR, если служба когда-либо возвращается с WebSockets на Long Polling. Если следующий запрос опроса не войдет MaxPollIntervalInSeconds, служба Azure SignalR очищает клиентское подключение. Обратите внимание, что служба Azure SignalR также очищает подключения при кэшировании размера буфера записи больше 1 МБ, чтобы обеспечить производительность службы. Значение MaxPollIntervalInSeconds по умолчанию составляет 5 секунд. Параметр ограничен 1–300 секунд.

Мы рекомендуем использовать WebSockets для серверных Blazor приложений, развернутых в службе приложение Azure. Служба SignalR Azure использует соединения WebSockets по умолчанию. Если приложение не использует службу Azure SignalR, см. статью Публикация приложения ASP.NET Core SignalR в Службе приложений Azure.

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

Настройка

Приложение, настраиваемое для службы Azure SignalR, должно поддерживать прикрепленные сеансы, когда клиенты перенаправляются обратно на тот же сервер при предварительной отрисовке. В качестве значения параметра 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.

Примечание.

В приложении, которое не включило прикрепленные сеансы для службы Azure SignalR, выдается следующее сообщение об ошибке:

blazor.server.js:1 Не перехвачено (в обещании) Ошибка: Вызов отменен из-за закрытия базового соединения.

Подготовка службы Azure SignalR

Чтобы подготовить службу Azure SignalR для приложения в Visual Studio, выполните приведенные ниже действия.

  1. Создайте профиль публикации приложений Azure Apps в Visual Studio для приложения .
  2. Добавьте в профиль зависимость службы Azure SignalR. Если подписка Azure не имеет уже существующего экземпляра службы Azure SignalR для назначения приложению, выберите Создайте новый экземпляр службы Azure SignalR для предоставления нового экземпляра службы.
  3. Публикация приложения в Azure.

При подготовке службы Azure SignalR в Visual Studio автоматически включаются прикрепленные сеансы и в конфигурацию службы приложения добавляется строка подключения SignalR.

Масштабируемость в приложениях контейнеров Azure

Масштабирование серверных Blazor приложений в приложениях контейнеров Azure требует конкретных рекомендаций в дополнение к использованию службы AzureSignalR. Из-за обработки маршрутизации запросов служба защиты данных ASP.NET Core должна быть настроена для сохранения ключей в централизованном расположении, к которому могут получить доступ все экземпляры контейнеров. Ключи можно хранить в Хранилище BLOB-объектов Azure и защищать с помощью Azure Key Vault. Служба защиты данных использует ключи для десериализации Razor компонентов.

Примечание.

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

  1. Чтобы настроить службу защиты данных для использования Хранилище BLOB-объектов Azure и Azure Key Vault, см. следующие пакеты NuGet:

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

    Примечание.

    Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по 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. Выберите службу Подключение or в области навигации слева.
    3. Нажмите кнопку "+ Создать " в верхней области навигации.
    4. В меню "Создание подключения" введите следующие значения:
      • Контейнер. Выберите приложение-контейнер, созданное для размещения приложения.
      • Тип службы: выбор служба хранилища BLOB-объектов.
      • Подписка: выберите подписку, которая владеет приложением-контейнером.
      • имя Подключение ion: введите имя scalablerazorstorage.
      • Тип клиента: выберите .NET и нажмите кнопку "Далее".
    5. Выберите управляемое удостоверение , назначаемое системой, и нажмите кнопку "Далее".
    6. Используйте параметры сети по умолчанию и нажмите кнопку "Далее".
    7. После проверки параметров в Azure нажмите кнопку Создать.

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

служба приложение Azure без Azure SignalR Службы

Blazor Размещение веб-приложения, использующего интерактивную отрисовку на стороне сервера в службе приложение Azure, требует настройки сопоставления маршрутизации запросов приложений (ARR) и WebSockets. Служба приложений также должны быть соответствующим образом распределены по всему миру, чтобы уменьшить задержку пользовательского интерфейса. Использование службы Azure SignalR при размещении в службе приложение Azure не требуется.

Blazor Server Для размещения приложения в службе приложение Azure требуется настройка сопоставления маршрутизации запросов приложений (ARR) и WebSockets. Служба приложений также должны быть соответствующим образом распределены по всему миру, чтобы уменьшить задержку пользовательского интерфейса. Использование службы Azure SignalR при размещении в службе приложение Azure не требуется.

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

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"

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

@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)) освобождает память?
  • Рассмотрите возможность выделения объектов в куче больших объектов.
  • Вы тестируете рост памяти после того, как приложение нагревается с помощью запросов и обработки? Как правило, кэши заполняются при первом выполнении кода, добавляющего постоянный объем памяти в место приложения.