Balanceamento de carga do lado do cliente do gRPC
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.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
O balanceamento de carga do lado do cliente é um recurso que permite que os clientes do gRPC distribuam a carga de forma ideal entre os servidores disponíveis. Esse artigo discute como configurar o balanceamento de carga do lado do cliente para criar aplicativos do gRPC escalonáveis e de alto desempenho no .NET.
O balanceamento de carga do lado do cliente exige:
- .NET 5 ou posterior.
Grpc.Net.Client
versão 2.45.0 ou posterior.
Configurar o balanceamento de carga do lado do cliente do gRPC
O balanceamento de carga do lado do cliente é configurado quando um canal é criado. Os dois componentes a serem considerados ao usar o balanceamento de carga:
- O resolvedor, que resolve os endereços do canal. Os resolvedores dão suporte à obtenção de endereços de uma fonte externa. Isso também é conhecido como descoberta de serviço.
- O balanceador de carga, que cria conexões e escolhe o endereço que uma chamada do gRPC usará.
Implementações internas de resolvedores e balanceadores de carga estão incluídas no Grpc.Net.Client
. O balanceamento de carga também pode ser estendido gravando resolvedores personalizados e balanceadores de carga.
Endereços, conexões e outro estado de balanceamento de carga são armazenados em uma instância de GrpcChannel
. Um canal deve ser reutilizado ao fazer chamadas do gRPC para que o balanceamento de carga funcione corretamente.
Observação
Algumas configurações de balanceamento de carga usam injeção de dependência (DI). Aplicativos que não usam DI podem criar uma instância ServiceCollection.
Se um aplicativo já tiver configuração de DI, como um site ASP.NET Core, os tipos deverão ser registrados com a instância de DI existente. GrpcChannelOptions.ServiceProvider
é configurado obtendo um IServiceProvider da ID.
Configurar resolvedor
O resolvedor é configurado usando o endereço com o qual um canal é criado. O esquema de URI do endereço especifica o resolvedor.
Esquema | Tipo | Descrição |
---|---|---|
dns |
DnsResolverFactory |
Resolve endereços consultando o nome do host para registros de endereço do DNS. |
static |
StaticResolverFactory |
Resolve os endereços especificados pelo aplicativo. Recomendado se um aplicativo já souber os endereços que ele chama. |
Um canal não chama diretamente um URI que corresponde a um resolvedor. Em vez disso, um resolvedor correspondente é criado e usado para resolve os endereços.
Por exemplo, usando GrpcChannel.ForAddress("dns:///my-example-host", new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure })
:
- O esquema
dns
é mapeado paraDnsResolverFactory
. Uma nova instância de um resolvedor do DNS é criada para o canal. - O resolvedor faz uma consulta ao DNS de
my-example-host
e obtém dois resultados:127.0.0.100
e127.0.0.101
. - O balanceador de carga usa
127.0.0.100:80
e127.0.0.101:80
para criar conexões e fazer chamadas do gRPC.
DnsResolverFactory
O DnsResolverFactory
cria um resolvedor projetado para obter endereços de uma fonte externa. A resolução do DNS é comumente usada para balancear a carga em instâncias de pod que têm serviços sem cabeça do Kubernetes.
var channel = GrpcChannel.ForAddress(
"dns:///my-example-host",
new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure });
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "world" });
O código anterior:
- Configura o canal criado com o endereço
dns:///my-example-host
.- O esquema
dns
é mapeado paraDnsResolverFactory
. my-example-host
é o nome do host a ser resolvido.- Nenhuma porta é especificada no endereço, portanto, as chamadas gRPC são enviadas para a porta 80. Essa é a porta padrão para canais não seguros. Opcionalmente, uma porta pode ser especificada após o nome do host. Por exemplo,
dns:///my-example-host:8080
configura chamadas gRPC a serem enviadas para a porta 8080.
- O esquema
- Não especifica um balanceador de carga. O canal usa como padrão um balanceador de carga de primeira opção.
- Inicia a chamada
SayHello
do gRPC:- O resolvedor do DNS obtém endereços para o nome do host de
my-example-host
. - O balanceador de carga de primeira opção tenta se conectar a um dos endereços resolvidos.
- A chamada é enviada para o primeiro endereço ao qual o canal se conecta com êxito.
- O resolvedor do DNS obtém endereços para o nome do host de
Cache de endereço do DNS
O desempenho é importante durante o balanceamento de carga. A latência da resolução de endereços é eliminada das chamadas do gRPC armazenando em cache os endereços. Um resolvedor será invocado ao fazer a primeira chamada do gRPC e chamadas subsequentes usam o cache.
Os endereços serão atualizados automaticamente se uma conexão for interrompida. A atualização é importante em cenários em que os endereços mudam em runtime. Por exemplo, no Kubernetes, um pod reiniciado dispara o resolvedor do DNS para atualizar e obter o novo endereço do pod.
Por padrão, um resolvedor do DNS será atualizado se uma conexão for interrompida. O resolvedor do DNS também pode, opcionalmente, atualizar-se em um intervalo periódico. Isso pode ser útil para detectar rapidamente novas instâncias de pod.
services.AddSingleton<ResolverFactory>(
sp => new DnsResolverFactory(refreshInterval: TimeSpan.FromSeconds(30)));
O código anterior cria um DnsResolverFactory
com um intervalo de atualização e o registra com injeção de dependência. Para obter mais informações sobre como usar um resolvedor configurado personalizado, consulte Configurar resolvedores personalizados e balanceadores de carga.
StaticResolverFactory
Um resolvedor estático é fornecido por StaticResolverFactory
. Esse resolvedor:
- Não chama uma fonte externa. Em vez disso, o aplicativo cliente configura os endereços.
- Foi projetado para situações em que um aplicativo já sabe os endereços que chama.
var factory = new StaticResolverFactory(addr => new[]
{
new BalancerAddress("localhost", 80),
new BalancerAddress("localhost", 81)
});
var services = new ServiceCollection();
services.AddSingleton<ResolverFactory>(factory);
var channel = GrpcChannel.ForAddress(
"static:///my-example-host",
new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
ServiceProvider = services.BuildServiceProvider()
});
var client = new Greet.GreeterClient(channel);
O código anterior:
- Cria um
StaticResolverFactory
. Esta fábrica sabe sobre dois endereços:localhost:80
elocalhost:81
. - Registra a fábrica com injeção de dependência (ID).
- Configura o canal criado com:
- O endereço
static:///my-example-host
. O esquemastatic
é mapeado para um resolvedor estático. - Define
GrpcChannelOptions.ServiceProvider
com o provedor de serviços de ID.
- O endereço
Esse exemplo cria um novo ServiceCollection para ID. Suponha que um aplicativo já tenha configuração de ID, como um site do ASP.NET Core. Nesse caso, os tipos devem ser registrados com a instância ID existente. GrpcChannelOptions.ServiceProvider
é configurado obtendo um IServiceProvider da ID.
Configurar o balanceador de carga
Um balanceador de carga é especificado em um service config
usando a coleção ServiceConfig.LoadBalancingConfigs
. Dois balanceadores de carga são internos e são mapeados para nomes de configuração do balanceador de carga:
Nome | Tipo | Descrição |
---|---|---|
pick_first |
PickFirstLoadBalancerFactory |
Tenta se conectar a endereços até que uma conexão seja feita com êxito. As chamadas do gRPC são todas feitas para a primeira conexão bem-sucedida. |
round_robin |
RoundRobinLoadBalancerFactory |
Tenta se conectar a todos os endereços. As chamadas do gRPC são distribuídas entre todas as conexões com êxito usando a lógica round robin. |
service config
é uma abreviação da configuração de serviço e é representada pelo tipo ServiceConfig
. Há algumas maneiras pelas quais um canal pode obter um service config
com um balanceador de carga configurado:
- Um aplicativo pode especificar um
service config
quando um canal é criado usandoGrpcChannelOptions.ServiceConfig
. - Como alternativa, um resolvedor pode resolver um
service config
para um canal. Esse recurso permite que uma fonte externa especifique como seus chamadores devem executar o balanceamento de carga. Se um resolvedor dá suporte à resolução de umservice config
for depende da implementação do resolvedor. Desabilite esse recurso comGrpcChannelOptions.DisableResolverServiceConfig
. - Se nenhum
service config
for fornecido, ou oservice config
não tiver um balanceador de carga configurado, o canal usará como padrãoPickFirstLoadBalancerFactory
.
var channel = GrpcChannel.ForAddress(
"dns:///my-example-host",
new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
ServiceConfig = new ServiceConfig { LoadBalancingConfigs = { new RoundRobinConfig() } }
});
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "world" });
O código anterior:
- Especifica um
RoundRobinLoadBalancerFactory
noservice config
. - Inicia a chamada
SayHello
do gRPC:DnsResolverFactory
cria um resolvedor que obtém endereços para o nome do hostmy-example-host
.- O balanceador de carga round-robin tenta se conectar a todos os endereços resolvidos.
- As chamadas do gRPC são distribuídas uniformemente usando a lógica round-robin.
Configurar credenciais do canal
Um canal deve saber se as chamadas do gRPC são enviadas usando segurança de transporte. http
e https
não fazem mais parte do endereço; o esquema agora especifica um resolvedor, portanto Credentials
deve ser configurado nas opções de canal ao usar o balanceamento de carga.
ChannelCredentials.SecureSsl
- As chamadas do gRPC são protegidas com armazenamento local de thread (TLS, na sigla em inglês). Equivalente a um endereçohttps
.ChannelCredentials.Insecure
- As chamadas do gRPC não usam segurança do transporte. Equivalente a um endereçohttp
.
var channel = GrpcChannel.ForAddress(
"dns:///my-example-host",
new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure });
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "world" });
Usar balanceamento de carga com a fábrica de clientes gRPC
A fábrica de clientes gRPC pode ser configurada para usar o balanceamento de carga:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("dns:///my-example-host");
})
.ConfigureChannel(o => o.Credentials = ChannelCredentials.Insecure);
builder.Services.AddSingleton<ResolverFactory>(
sp => new DnsResolverFactory(refreshInterval: TimeSpan.FromSeconds(30)));
var app = builder.Build();
O código anterior:
- Configura o cliente com um endereço de balanceamento de carga.
- Especifica credenciais de canal.
- Registra tipos de DI com o IServiceCollection do aplicativo.
Gravar resolvedores personalizados e balanceadores de carga
O balanceamento de carga do lado do cliente é extensível:
- Implemente
Resolver
para criar um resolvedor personalizado e resolva endereços de uma nova fonte de dados. - Implemente
LoadBalancer
para criar um balanceador de carga personalizado com novo comportamento de balanceamento de carga.
Importante
As APIs usadas para estender o balanceamento de carga do lado do cliente são experimentais. Elas podem ser alteradas sem aviso prévio.
Criar um resolvedor personalizado
Um resolvedor:
- Implementa
Resolver
e é criado por umResolverFactory
. Crie um resolvedor personalizado implementando esses tipos. - É responsável por resolver os endereços que um balanceador de carga usa.
- Como opção, pode fornecer uma configuração de serviço.
public class FileResolver : PollingResolver
{
private readonly Uri _address;
private readonly int _port;
public FileResolver(Uri address, int defaultPort, ILoggerFactory loggerFactory)
: base(loggerFactory)
{
_address = address;
_port = defaultPort;
}
public override async Task ResolveAsync(CancellationToken cancellationToken)
{
// Load JSON from a file on disk and deserialize into endpoints.
var jsonString = await File.ReadAllTextAsync(_address.LocalPath);
var results = JsonSerializer.Deserialize<string[]>(jsonString);
var addresses = results.Select(r => new BalancerAddress(r, _port)).ToArray();
// Pass the results back to the channel.
Listener(ResolverResult.ForResult(addresses));
}
}
public class FileResolverFactory : ResolverFactory
{
// Create a FileResolver when the URI has a 'file' scheme.
public override string Name => "file";
public override Resolver Create(ResolverOptions options)
{
return new FileResolver(options.Address, options.DefaultPort, options.LoggerFactory);
}
}
No código anterior:
FileResolverFactory
implementaResolverFactory
. Ele mapeia para o esquemafile
e cria instâncias deFileResolver
.FileResolver
implementaPollingResolver
.PollingResolver
é um tipo base abstrato que facilita a implementação de um resolvedor com lógica assíncrona substituindoResolveAsync
.- Em:
ResolveAsync
- O URI do arquivo é convertido em um caminho local. Por exemplo,
file:///c:/addresses.json
torna-sec:\addresses.json
. - JSON é carregado do disco e convertido em uma coleção de endereços.
- O ouvinte é chamado com resultados para informar ao canal que os endereços estão disponíveis.
- O URI do arquivo é convertido em um caminho local. Por exemplo,
Criar um balanceador de carga personalizado
Um balanceador de carga:
- Implementa
LoadBalancer
e é criado por umLoadBalancerFactory
. Crie um balanceador de carga personalizado e uma fábrica implementando esses tipos. - São dados endereços de um resolvedor e cria instâncias de
Subchannel
. - Rastreia o estado sobre a conexão e cria um
SubchannelPicker
. O canal usa internamente o seletor para escolher endereços ao fazer chamadas do gRPC.
O SubchannelsLoadBalancer
é:
- Uma classe base abstrata que implementa o
LoadBalancer
. - Gerencia a criação de instâncias de
Subchannel
dos endereços. - Facilita a implementação de uma política de seleção personalizada em uma coleção de subcanais.
public class RandomBalancer : SubchannelsLoadBalancer
{
public RandomBalancer(IChannelControlHelper controller, ILoggerFactory loggerFactory)
: base(controller, loggerFactory)
{
}
protected override SubchannelPicker CreatePicker(List<Subchannel> readySubchannels)
{
return new RandomPicker(readySubchannels);
}
private class RandomPicker : SubchannelPicker
{
private readonly List<Subchannel> _subchannels;
public RandomPicker(List<Subchannel> subchannels)
{
_subchannels = subchannels;
}
public override PickResult Pick(PickContext context)
{
// Pick a random subchannel.
return PickResult.ForSubchannel(_subchannels[Random.Shared.Next(0, _subchannels.Count)]);
}
}
}
public class RandomBalancerFactory : LoadBalancerFactory
{
// Create a RandomBalancer when the name is 'random'.
public override string Name => "random";
public override LoadBalancer Create(LoadBalancerOptions options)
{
return new RandomBalancer(options.Controller, options.LoggerFactory);
}
}
No código anterior:
RandomBalancerFactory
implementaLoadBalancerFactory
. Mapeia para o nome da políticarandom
e cria instâncias deRandomBalancer
.RandomBalancer
implementaSubchannelsLoadBalancer
. Cria umRandomPicker
que escolhe aleatoriamente um subcanal.
Configurar resolvedores personalizados e balanceadores de carga
Resolvedores personalizados e balanceadores de carga precisam ser registrados com injeção de dependência (ID) quando são usados. Há duas opções:
- Se um aplicativo já estiver usando ID, como um aplicativo Web ASP.NET Core, ele poderá ser registrado com a configuração de ID existente. Um IServiceProvider pode ser resolvido a partir da ID e aprovado para o canal usando
GrpcChannelOptions.ServiceProvider
. - Se um aplicativo não estiver usando ID, crie:
- Um ServiceCollection com tipos registrados com ele.
- Um provedor de serviços usando BuildServiceProvider.
var services = new ServiceCollection();
services.AddSingleton<ResolverFactory, FileResolverFactory>();
services.AddSingleton<LoadBalancerFactory, RandomLoadBalancerFactory>();
var channel = GrpcChannel.ForAddress(
"file:///c:/data/addresses.json",
new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
ServiceConfig = new ServiceConfig { LoadBalancingConfigs = { new LoadBalancingConfig("random") } },
ServiceProvider = services.BuildServiceProvider()
});
var client = new Greet.GreeterClient(channel);
O código anterior:
- Cria um
ServiceCollection
e registra novas implementações de resolvedor e balanceador de carga. - Cria um canal configurado para usar as novas implementações:
ServiceCollection
é integrado a umIServiceProvider
e definido comoGrpcChannelOptions.ServiceProvider
.- O endereço do canal é
file:///c:/data/addresses.json
. O esquemafile
é mapeado paraFileResolverFactory
. - O nome do balanceador de carga
service config
érandom
. Mapeia paraRandomLoadBalancerFactory
.
Por que o balanceamento de carga é importante
O HTTP/2 multiplexa várias chamadas em uma única conexão TCP. Se gRPC e HTTP/2 forem usados com um balanceador de carga de rede (NLB, na sigla em inglês), a conexão será encaminhada para um servidor e todas as chamadas do gRPC serão enviadas para esse servidor. As outras instâncias de servidor no NLB ficam ociosas.
Os balanceadores de carga de rede são uma solução comum para balanceamento de carga porque são rápidos e leves. Por exemplo, o Kubernetes, por padrão, usa um balanceador de carga de rede para equilibrar conexões entre instâncias de pod. No entanto, os balanceadores de carga de rede não são eficazes na distribuição de carga quando usados com gRPC e HTTP/2.
Balanceamento de carga do lado do cliente ou de proxy?
gRPC e HTTP/2 podem ser efetivamente balanceados por carga usando um proxy do balanceador de carga do aplicativo ou um balanceamento de carga do lado do cliente. Ambas as opções permitem que chamadas do gRPC individuais sejam distribuídas entre servidores disponíveis. Decidir entre o balanceamento de carga de proxy e o balanceamento de carga do lado do cliente é uma opção arquitetônica. Há prós e contras para cada um.
Proxy: as chamadas do gRPC são enviadas para o proxy; o proxy toma uma decisão de balanceamento de carga e a chamada do gRPC é enviada para o ponto de extremidade final. O proxy é responsável por saber sobre pontos de extremidade. O uso de um proxy adiciona:
- Um salto de rede adicional para chamadas do gRPC.
- Latência e consome recursos adicionais.
- O servidor proxy deve ser configurado e configurado corretamente.
Balanceamento de carga do lado do cliente: o cliente gRPC toma uma decisão de balanceamento de carga quando uma chamada do gRPC é iniciada. A chamada do gRPC é enviada diretamente para o ponto de extremidade final. Quando usar o balanceamento de carga do lado do cliente:
- O cliente é responsável por saber sobre os pontos de extremidade disponíveis e tomar decisões de balanceamento de carga.
- Configuração adicional do cliente é necessária.
- Chamadas do gRPC com balanceamento de carga de alto desempenho eliminam a necessidade de um proxy.