Partilhar via


Hospedar e implantar aplicativos Blazor do lado do servidor

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como hospedar e implantar aplicativos Blazor do lado do servidor (aplicativosBlazor Web Apps e Blazor Server) usando o ASP.NET Core.

Valores de configuração do host

Os aplicativos Blazor do lado do servidor podem aceitar valores de configuração de Host Genérico.

Implantação

Usando um modelo de hospedagem do lado do servidor, Blazor é executado no servidor de dentro de um aplicativo ASP.NET Core. Atualizações da interface do usuário, manipulação de eventos e chamadas JavaScript são tratadas por meio de uma conexão SignalR.

É necessário um servidor Web capaz de hospedar um aplicativo ASP.NET Core. O Visual Studio inclui um modelo de projeto de aplicativo do lado do servidor. Para obter mais informações sobre Blazor modelos de projeto, consulte ASP.NET Core Blazor estrutura do projeto.

Publique uma aplicação na configuração Release e implante o conteúdo da pasta bin/Release/{TARGET FRAMEWORK}/publish, onde o espaço reservado {TARGET FRAMEWORK} é o framework de destino.

Escalabilidade

Ao considerar a escalabilidade de um único servidor (scale-up), a memória disponível para um aplicativo é provavelmente o primeiro recurso que o aplicativo esgota à medida que as demandas do usuário aumentam. A memória disponível no servidor afeta o:

  • Número de circuitos ativos que um servidor pode suportar.
  • Latência da interface do usuário no cliente.

Para obter orientação sobre como criar aplicativos Blazor do lado do servidor seguros e escaláveis, consulte os seguintes recursos:

Cada circuito usa aproximadamente 250 KB de memória para uma aplicação mínima ao estilo Hello World. O tamanho de um circuito depende do código do aplicativo e dos requisitos de manutenção de estado associados a cada componente. Recomendamos que você meça as demandas de recursos durante o desenvolvimento para seu aplicativo e infraestrutura, mas a linha de base a seguir pode ser um ponto de partida no planejamento de sua meta de implantação: Se você espera que seu aplicativo ofereça suporte a 5.000 usuários simultâneos, considere orçar pelo menos 1,3 GB de memória do servidor para o aplicativo (ou ~273 KB por usuário).

SignalR configuração

As condições de hospedagem e dimensionamento de SignalRdo aplicam-se a aplicativos Blazor que usam SignalR.

Para obter mais informações sobre SignalR em aplicativos Blazor, incluindo diretrizes de configuração, consulte ASP.NET Core BlazorSignalR guidance.

Transportes

Blazor funciona melhor ao usar WebSockets como o transporte de SignalR devido à menor latência, melhor confiabilidade e melhor segurança . Sondagem Longa é utilizada por SignalR quando WebSockets não está disponível ou quando a aplicação está explicitamente configurada para usar Sondagem Longa.

Um aviso de console será exibido se for utilizado o Long Polling.

Falhou ao conectar via WebSockets, utilizando o transporte de reserva Long Polling. Isso pode ser devido a uma VPN ou proxy bloqueando a conexão.

Falhas globais de implantação e conexão

Recomendações para implantações globais em data centers geográficos:

  • Implante o aplicativo nas regiões onde a maioria dos usuários reside.
  • Leve em consideração o aumento da latência para o tráfego entre continentes. Para controlar a aparência da interface do usuário de reconexão, consulte ASP.NET Core BlazorSignalR guidance.
  • Considere usar o Azure SignalR Service.

Serviço de Aplicativo do Azure

A hospedagem no Serviço de Aplicações do Azure requer a configuração de WebSockets e afinidade de sessão, também conhecida como afinidade ARR (Application Request Routing).

Observação

Uma aplicação Blazor no Azure App Service não requer do Serviço de SignalR do Azure.

Habilite o seguinte para o registro do aplicativo no Serviço de Aplicativo do Azure:

  • WebSockets para permitir que o transporte WebSockets funcione. A configuração padrão é Desativado.
  • Afinidade de sessão para direcionar as solicitações de um utilizador de volta para a mesma instância do App Service. A configuração padrão é On.
  1. No portal do Azure, navegue até a aplicação web em Serviços de Aplicações.
  2. Abra Configurações>Configurações.
  3. Defina de soquetes da Web como On.
  4. Verifique se de afinidade de sessão está definido como On.

Azure SignalR Service

O opcional Serviço de do Azure funciona em conjunto com o hub de do aplicativo para dimensionar um aplicativo do lado do servidor para um grande número de conexões simultâneas. Além disso, o alcance global do serviço e os data centers de alto desempenho ajudam significativamente na redução da latência devido à geografia.

O serviço não é necessário para Blazor aplicativos hospedados no Serviço de Aplicativo do Azure ou nos Aplicativos de Contêiner do Azure, mas pode ser útil em outros ambientes de hospedagem:

  • Para facilitar a expansão da conexão.
  • Gerir a distribuição global.

O Serviço SignalR do Azure com SDK v1.26.1 ou posterior dá suporte a SignalR de reconexão com monitoração de estado (WithStatefulReconnect).

Caso o aplicativo use Sondagem Longa ou volte para Sondagem Longa em vez de WebSockets, talvez seja necessário configurar o intervalo máximo de sondagem (MaxPollIntervalInSeconds, padrão: 5 segundos, limite: 1-300 segundos), que define o intervalo máximo de sondagem permitido para conexões de Sondagem Longa no Serviço de SignalR do Azure. Se a próxima solicitação de sondagem não chegar dentro do intervalo máximo de sondagem, o serviço fechará a conexão do cliente.

Para obter orientação sobre como adicionar o serviço como uma dependência a uma implantação de produção, consulte Publicar um aplicativo ASP.NET Core SignalR no Serviço de Aplicativo do Azure.

Para mais informações, consulte:

Aplicativos de contêiner do Azure

Para obter uma exploração mais profunda do dimensionamento de aplicativos Blazor do lado do servidor no serviço Aplicativos de Contêiner do Azure, consulte Dimensionamento ASP.NET aplicativos principais no Azure. O tutorial explica como criar e integrar os serviços necessários para hospedar aplicativos em Aplicativos de Contêiner do Azure. As etapas básicas também são fornecidas nesta seção.

  1. Configure o serviço Aplicativos de Contêiner do Azure para afinidade de sessão seguindo as orientações em Afinidade de Sessão em Aplicativos de Contêiner do Azure (documentação do Azure).

  2. O serviço ASP.NET Core Data Protection (DP) deve ser configurado para manter chaves em um local centralizado que todas as instâncias de contêiner possam acessar. As chaves podem ser armazenadas no Armazenamento de Blobs do Azure e protegidas com o Cofre de Chaves do Azure. O serviço DP usa as chaves para desserializar os componentes Razor. Para configurar o serviço de DP para usar o Armazenamento de Blobs do Azure e o Cofre de Chaves do Azure, faça referência aos seguintes pacotes NuGet:

    Observação

    Para obter orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes em Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

  3. Atualize Program.cs com o seguinte código destacado:

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

    As alterações anteriores permitem que o aplicativo gerencie o serviço de PD usando uma arquitetura centralizada e escalável. DefaultAzureCredential descobre a aplicação de contentor gerida identity depois de o código ser implementado no Azure e utiliza-a para se conectar ao armazenamento de blobs e ao cofre de chaves da aplicação.

  4. Para criar o aplicativo de contêiner gerenciado identity e conceder-lhe acesso ao armazenamento de blob e a um cofre de chaves, conclua as seguintes etapas:

    1. No Portal do Azure, navegue até a página de visão geral do aplicativo de contêiner.
    2. Selecione Service Connector na navegação à esquerda.
    3. Selecione e depois Criar na navegação superior.
    4. No menu de expansão Criar conexão, insira os seguintes valores:
      • Container: Selecione o aplicativo de contêiner que você criou para hospedar seu aplicativo.
      • Tipo de serviço: Selecione Blob Storage.
      • Subscrição: Selecione a subscrição proprietária da aplicação do contentor.
      • Nome da conexão: Digite um nome de scalablerazorstorage.
      • Tipo de cliente: Selecione .NET e, em seguida, selecione Avançar.
    5. Selecione identity gerenciado atribuído ao sistema e selecione Avançar.
    6. Use as configurações de rede padrão e selecione Avançar.
    7. Depois que o Azure validar as configurações, selecione Criar.

    Repita as configurações anteriores para o cofre de chaves. Selecione o serviço de cofre de chaves apropriado e a chave na guia Noções básicas.

IIS

Ao usar o IIS, habilite:

Para obter mais informações, consulte as orientações e os links cruzados de recursos externos do IIS em Publicar um aplicativo ASP.NET Core no IIS.

Kubernetes

Crie uma definição de ingresso com as seguintes anotações do Kubernetes para afinidade de sessão:

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 com Nginx

Siga as orientações para um aplicativo ASP.NET Core SignalR com as seguintes alterações:

  • Altere o caminho location de /hubroute (location /hubroute { ... }) para o caminho raiz / (location / { ... }).
  • Remova a configuração para a bufferização de proxy (proxy_buffering off;) porque a configuração só se aplica a eventos Server-Sent (SSE), que não são relevantes para as interações cliente-servidor do aplicativo Blazor.

Para obter mais informações e orientações de configuração, consulte os seguintes recursos:

Linux com Apache

Para hospedar um aplicativo Blazor atrás do Apache no Linux, configure ProxyPass para tráfego HTTP e WebSockets.

No exemplo a seguir:

  • Kestrel servidor está em execução na máquina host.
  • O aplicativo escuta o tráfego na porta 5000.
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

Habilite os seguintes módulos:

a2enmod   proxy
a2enmod   proxy_wstunnel

Verifique se há erros de WebSockets no console do navegador. Exemplos de erros:

  • O Firefox não consegue estabelecer uma conexão com o servidor em ws://the-domain-name.tld/_blazor?id=XXX
  • Erro: Falha ao iniciar o transporte 'WebSockets': Erro: Houve um erro com o transporte.
  • Erro: Falha ao iniciar o transporte 'LongPolling': TypeError: this.transport não está definido
  • Erro: Não é possível conectar-se ao servidor com qualquer um dos transportes disponíveis. WebSockets falharam
  • Erro: Não é possível enviar dados se a conexão não estiver no estado 'Conectado'.

Para obter mais informações e orientações de configuração, consulte os seguintes recursos:

Meça a latência da rede

JS interoperabilidade podem ser usados para medir a latência da rede, como o exemplo a seguir demonstra.

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

Para uma experiência de interface do usuário razoável, recomendamos uma latência sustentada da interface do usuário de 250 ms ou menos.

Gestão de memória

No servidor, um novo circuito é criado para cada sessão do usuário. Cada sessão de usuário corresponde à renderização de um único documento no navegador. Por exemplo, várias guias criam várias sessões.

Blazor mantém uma conexão constante com o navegador, chamado de circuito , que iniciou a sessão. As conexões podem ser perdidas a qualquer momento por qualquer um dos vários motivos, como quando o usuário perde a conectividade de rede ou fecha abruptamente o navegador. Quando uma conexão é perdida, Blazor tem um mecanismo de recuperação que coloca um número limitado de circuitos em um pool "desconectado", dando aos clientes uma quantidade limitada de tempo para reconectar e restabelecer a sessão (padrão: 3 minutos).

Após esse tempo, Blazor libera o circuito e descarta a sessão. A partir desse ponto, o circuito é elegível para coleta de lixo (GC) e é reivindicado quando uma coleta para a geração de GC do circuito é acionada. Um aspeto importante a entender é que os circuitos têm uma longa vida útil, o que significa que a maioria dos objetos enraizados pelo circuito eventualmente atingem a Gen 2. Como resultado, você pode não ver esses objetos liberados até que uma coleção Gen 2 aconteça.

Meça o uso de memória em geral

Pré-requisitos:

  • O aplicativo deve ser publicado na configuração Release. Medidas de configuração de depuração não são relevantes, pois o código gerado não é representativo do código usado para uma implementação de produção.
  • O aplicativo deve ser executado sem um depurador anexado, pois isso também pode afetar o comportamento do aplicativo e estragar os resultados. No Visual Studio, inicie o aplicativo sem depuração, selecionando Depurar>Iniciar sem Depurar na barra de menus ou Ctrl+F5 usando o teclado.
  • Considere os diferentes tipos de memória para entender quanta memória é realmente usada pelo .NET. Geralmente, os desenvolvedores inspecionam o uso da memória do aplicativo no Gerenciador de Tarefas no sistema operacional Windows, que normalmente oferece um limite superior da memória real em uso. Para mais informações, consulte os seguintes artigos:

Uso de memória aplicado a Blazor

Calculamos a memória usada por blazor da seguinte forma:

(Circuitos ativos × Memória por circuito) + (Circuitos desconectados × Memória por circuito)

A quantidade de memória que um circuito usa e o máximo potencial de circuitos ativos que um aplicativo pode manter depende em grande parte de como o aplicativo é escrito. O número máximo de circuitos ativos possíveis é descrito aproximadamente por:

Memória máxima disponível / Memória por circuito = Máximo potencial de circuitos ativos

Para que ocorra uma fuga de memória no Blazor, o seguinte tem de ser verdadeiro:

  • A memória deve ser alocada pela estrutura, não pelo aplicativo. Se você alocar uma matriz de 1 GB no aplicativo, o aplicativo deverá gerenciar o descarte da matriz.
  • A memória não deve ser usada ativamente, o que significa que o circuito não está ativo e foi removido do cache de circuitos desconectados. Se você tiver o máximo de circuitos ativos em execução, ficar sem memória é um problema de escala, não um vazamento de memória.
  • Uma coleta de lixo (GC) para a geração do circuito foi realizada, mas o coletor de lixo não conseguiu reivindicar o circuito porque outro objeto no framework mantém uma forte referência ao circuito.

Em outros casos, não há vazamento de memória. Se o circuito estiver ativo (conectado ou desconectado), o circuito ainda está em uso.

Se uma coleta para a geração de GC do circuito não for executada, a memória não será liberada porque o coletor de lixo não precisa liberar a memória naquele momento.

Se uma coleção para uma geração GC for executada e liberar o circuito, você deverá validar a memória em relação às estatísticas GC, não o processo, pois o .NET pode decidir manter a memória virtual ativa.

Se a memória não estiver liberada, você deverá encontrar um circuito que não esteja ativo ou desconectado e que esteja enraizado por outro objeto na estrutura. Em qualquer outro caso, a incapacidade de liberar memória é um problema do aplicativo no código do desenvolvedor.

Reduzir o uso de memória

Adote qualquer uma das seguintes estratégias para reduzir o uso de memória de um aplicativo:

  • Limite a quantidade total de memória usada pelo processo .NET. Para obter mais informações, consulte Opções de configuração do Runtime para coleta de lixo.
  • Reduza o número de circuitos desligados.
  • Reduza o tempo que um circuito pode estar no estado desconectado.
  • Acione uma coleta de lixo manualmente para executar uma coleta durante os períodos de inatividade.
  • Configure a coleta de lixo no modo Estação de trabalho, que aciona agressivamente a coleta de lixo, em vez do modo Servidor.

Tamanho da pilha para alguns navegadores de dispositivos móveis

Ao criar um aplicativo Blazor que é executado no cliente e direcionado a navegadores de dispositivos móveis, especialmente o Safari no iOS, pode ser necessário diminuir a memória máxima para o aplicativo com a propriedade MSBuild EmccMaximumHeapSize. Para obter mais informações, consulte Host e implantar ASP.NET Core Blazor WebAssembly.

Ações e considerações adicionais

  • Capture um despejo de memória do processo quando as demandas de memória são altas e identifique os objetos que estão recebendo mais memória e onde esses objetos estão enraizados (o que contém uma referência a eles).
  • Você pode examinar as estatísticas sobre como a memória em seu aplicativo está se comportando usando dotnet-counters. Para obter mais informações, consulte Investigue contadores de desempenho (dotnet-counters).
  • Mesmo quando um GC é acionado, o .NET mantém a memória em vez de devolvê-la ao sistema operacional imediatamente, pois é provável que ele reutilize a memória em um futuro próximo. Isso evita cometer e descomprometer a memória constantemente, o que é caro. Você verá isso refletido se usar dotnet-counters, pois verá os GCs ocorrerem e a quantidade de memória utilizada reduzir para 0 (zero), mas não verá o contador do conjunto de trabalho diminuir, o que indica que o .NET está retendo a memória para reutilizá-la. Para obter mais informações sobre as configurações do arquivo de projeto (.csproj) para controlar esse comportamento, consulte Runtime configuration options for garbage collection.
  • O Server GC não aciona coletas de lixo até determinar que é absolutamente necessário fazê-lo para evitar congelar seu aplicativo e considera que seu aplicativo é a única coisa em execução na máquina, para que possa usar toda a memória no sistema. Se o sistema tiver 50 GB, o coletor de lixo procura usar os 50 GB completos de memória disponível antes de acionar uma coleta Gen 2.
  • Para obter informações sobre a configuração de retenção de circuito desconectado, consulte ASP.NET Core BlazorSignalR guidance.

Medição da memória

  • Publique a aplicação na configuração Release.
  • Execute uma versão publicada do aplicativo.
  • Não anexe um depurador ao aplicativo em execução.
  • Iniciar uma coleta de compactação forçada Gen 2 (GC.Collect(2, GCCollectionMode.Aggressive | GCCollectionMode.Forced, blocking: true, compacting: true))) irá liberar a memória?
  • Considere se seu aplicativo está alocando objetos no heap de objeto grande.
  • Você está testando o crescimento da memória depois que o aplicativo é aquecido com solicitações e processamento? Normalmente, há caches que são preenchidos quando o código é executado pela primeira vez que adicionam uma quantidade constante de memória ao espaço ocupado pelo aplicativo.