bilanciamento del carico lato client gRPC
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Il bilanciamento del carico lato client è una funzionalità che consente ai client gRPC di distribuire il carico in modo ottimale tra i server disponibili. Questo articolo illustra come configurare il bilanciamento del carico lato client per creare app gRPC a prestazioni elevate e scalabili in .NET.
Il bilanciamento del carico lato client richiede:
- .NET 5 o versione successiva.
Grpc.Net.Client
versione 2.45.0 o successiva.
Configurare il bilanciamento del carico lato client gRPC
Il bilanciamento del carico lato client viene configurato quando viene creato un canale. I due componenti da considerare quando si usa il bilanciamento del carico:
- Resolver, che risolve gli indirizzi per il canale. I resolver supportano il recupero di indirizzi da un'origine esterna. Questa operazione è nota anche come individuazione dei servizi.
- Il servizio di bilanciamento del carico, che crea connessioni e seleziona l'indirizzo che verrà usato da una chiamata gRPC.
Le implementazioni predefinite di resolver e servizi di bilanciamento del carico sono incluse in Grpc.Net.Client
. Il bilanciamento del carico può essere esteso anche scrivendo resolver personalizzati e servizi di bilanciamento del carico.
Gli indirizzi, le connessioni e altri stati di bilanciamento del carico vengono archiviati in un'istanza GrpcChannel
di . Un canale deve essere riutilizzato quando si effettuano chiamate gRPC per il corretto funzionamento del bilanciamento del carico.
Nota
Alcune configurazioni di bilanciamento del carico usano l'inserimento delle dipendenze. Le app che non usano l'inserimento delle dipendenze possono creare un'istanza ServiceCollection di .
Se un'app ha già la configurazione dell'inserimento delle dipendenze, ad esempio un sito Web di ASP.NET Core, i tipi devono essere registrati con l'istanza di inserimento delle dipendenze esistente. GrpcChannelOptions.ServiceProvider
viene configurato recuperando un'istanza dall'inserimento delle dipendenze IServiceProvider .
Configurare il sistema di risoluzione
Il sistema di risoluzione viene configurato usando l'indirizzo con cui viene creato un canale. Lo schema URI dell'indirizzo specifica il resolver.
Schema | Tipo | Descrizione |
---|---|---|
dns |
DnsResolverFactory |
Risolve gli indirizzi eseguendo una query sul nome host per i record di indirizzi DNS. |
static |
StaticResolverFactory |
Risolve gli indirizzi specificati dall'app. Consigliato se un'app conosce già gli indirizzi che chiama. |
Un canale non chiama direttamente un URI che corrisponde a un resolver. Viene invece creato e usato un sistema di risoluzione corrispondente per risolvere gli indirizzi.
Ad esempio, usando GrpcChannel.ForAddress("dns:///my-example-host", new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure })
:
- Lo schema esegue il
dns
mapping aDnsResolverFactory
. Viene creata una nuova istanza di un resolver DNS per il canale. - Il resolver esegue una query DNS per
my-example-host
e ottiene due risultati:127.0.0.100
e127.0.0.101
. - Il servizio di bilanciamento del carico usa
127.0.0.100:80
e127.0.0.101:80
per creare connessioni ed effettuare chiamate gRPC.
DnsResolverFactory
DnsResolverFactory
Crea un sistema di risoluzione progettato per ottenere gli indirizzi da un'origine esterna. La risoluzione DNS viene comunemente usata per bilanciare il carico sulle istanze di pod che dispongono di servizi headless 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" });
Il codice precedente:
- Configura il canale creato con l'indirizzo
dns:///my-example-host
.- Lo schema esegue il
dns
mapping aDnsResolverFactory
. my-example-host
è il nome host da risolvere.- Nessuna porta specificata nell'indirizzo, quindi le chiamate gRPC vengono inviate alla porta 80. Questa è la porta predefinita per i canali non protetti. È possibile specificare una porta facoltativamente dopo il nome host. Ad esempio,
dns:///my-example-host:8080
configura le chiamate gRPC da inviare alla porta 8080.
- Lo schema esegue il
- Non specifica un servizio di bilanciamento del carico. Per impostazione predefinita, il canale è un servizio di bilanciamento del carico pick first.
- Avvia la chiamata
SayHello
gRPC :- Il resolver DNS ottiene gli indirizzi per il nome
my-example-host
host . - Selezionare il primo servizio di bilanciamento del carico tenta di connettersi a uno degli indirizzi risolti.
- La chiamata viene inviata al primo indirizzo a cui si connette correttamente il canale.
- Il resolver DNS ottiene gli indirizzi per il nome
Memorizzazione nella cache degli indirizzi DNS
Le prestazioni sono importanti durante il bilanciamento del carico. La latenza di risoluzione degli indirizzi viene eliminata dalle chiamate gRPC memorizzando nella cache gli indirizzi. Un resolver verrà richiamato quando si effettua la prima chiamata gRPC e le chiamate successive usano la cache.
Gli indirizzi vengono aggiornati automaticamente se una connessione viene interrotta. L'aggiornamento è importante negli scenari in cui gli indirizzi cambiano in fase di esecuzione. Ad esempio, in Kubernetes un pod riavviato attiva il resolver DNS per aggiornare e ottenere il nuovo indirizzo del pod.
Per impostazione predefinita, un sistema di risoluzione DNS viene aggiornato se una connessione viene interrotta. Il sistema di risoluzione DNS può anche essere aggiornato facoltativamente in base a un intervallo periodico. Ciò può essere utile per rilevare rapidamente nuove istanze di pod.
services.AddSingleton<ResolverFactory>(
sp => new DnsResolverFactory(refreshInterval: TimeSpan.FromSeconds(30)));
Il codice precedente crea un oggetto DnsResolverFactory
con un intervallo di aggiornamento e lo registra con l'inserimento delle dipendenze. Per altre informazioni sull'uso di un resolver configurato personalizzato, vedere Configurare resolver e servizi di bilanciamento del carico personalizzati.
StaticResolverFactory
Un sistema di risoluzione statico viene fornito da StaticResolverFactory
. Questo sistema di risoluzione:
- Non chiama un'origine esterna. L'app client configura invece gli indirizzi.
- È progettato per situazioni in cui un'app conosce già gli indirizzi che chiama.
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);
Il codice precedente:
- Crea un oggetto
StaticResolverFactory
. Questa factory conosce due indirizzi:localhost:80
elocalhost:81
. - Registra la factory con inserimento delle dipendenze( DI).
- Configura il canale creato con:
- Indirizzo
static:///my-example-host
. Lo schema esegue ilstatic
mapping a un sistema di risoluzione statico. - Imposta
GrpcChannelOptions.ServiceProvider
con il provider di servizi di inserimento delle dipendenze.
- Indirizzo
In questo esempio viene creato un nuovo ServiceCollection oggetto per l'inserimento delle dipendenze. Si supponga che un'app abbia già la configurazione dell'inserimento delle dipendenze, ad esempio un sito Web di ASP.NET Core. In tal caso, i tipi devono essere registrati con l'istanza di inserimento delle dipendenze esistente. GrpcChannelOptions.ServiceProvider
viene configurato recuperando un'istanza dall'inserimento delle dipendenze IServiceProvider .
Configurare il bilanciamento del carico
Un servizio di bilanciamento del carico viene specificato in un service config
oggetto utilizzando la ServiceConfig.LoadBalancingConfigs
raccolta . Due servizi di bilanciamento del carico sono incorporati ed eseguono il mapping ai nomi di configurazione del servizio di bilanciamento del carico:
Nome | Tipo | Descrizione |
---|---|---|
pick_first |
PickFirstLoadBalancerFactory |
Tenta di connettersi agli indirizzi fino a quando non viene stabilita una connessione. Tutte le chiamate gRPC vengono effettuate alla prima connessione riuscita. |
round_robin |
RoundRobinLoadBalancerFactory |
Tenta di connettersi a tutti gli indirizzi. Le chiamate gRPC vengono distribuite in tutte le connessioni riuscite usando la logica round robin . |
service config
è un'abbreviazione della configurazione del servizio ed è rappresentata dal ServiceConfig
tipo . Esistono due modi per ottenere un service config
canale con un servizio di bilanciamento del carico configurato:
- Un'app può specificare un oggetto
service config
quando viene creato un canale usandoGrpcChannelOptions.ServiceConfig
. - In alternativa, un sistema di risoluzione può risolvere un oggetto
service config
per un canale. Questa funzionalità consente a un'origine esterna di specificare il modo in cui i chiamanti devono eseguire il bilanciamento del carico. Indica se un resolver supporta la risoluzione di unservice config
oggetto dipende dall'implementazione del resolver. Disabilitare questa funzionalità conGrpcChannelOptions.DisableResolverServiceConfig
. - Se non viene specificato alcun valore
service config
oservice config
se non è configurato un servizio di bilanciamento del carico, per impostazione predefinita il canale èPickFirstLoadBalancerFactory
.
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" });
Il codice precedente:
- Specifica un oggetto
RoundRobinLoadBalancerFactory
inservice config
. - Avvia la chiamata
SayHello
gRPC :DnsResolverFactory
crea un sistema di risoluzione che ottiene gli indirizzi per il nomemy-example-host
host .- Il servizio di bilanciamento del carico round robin tenta di connettersi a tutti gli indirizzi risolti.
- Le chiamate gRPC vengono distribuite in modo uniforme usando la logica round robin.
Configurare le credenziali del canale
Un canale deve sapere se le chiamate gRPC vengono inviate usando la sicurezza del trasporto. http
e https
non fanno più parte dell'indirizzo, lo schema ora specifica un sistema di risoluzione, quindi Credentials
deve essere configurato nelle opzioni del canale quando si usa il bilanciamento del carico.
ChannelCredentials.SecureSsl
- le chiamate gRPC sono protette con Transport Layer Security (TLS). Equivalente a unhttps
indirizzo.ChannelCredentials.Insecure
- le chiamate gRPC non usano la sicurezza del trasporto. Equivalente a unhttp
indirizzo.
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" });
Usare il bilanciamento del carico con la factory client gRPC
La factory client gRPC può essere configurata per l'uso del bilanciamento del carico:
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();
Il codice precedente:
- Configura il client con un indirizzo di bilanciamento del carico.
- Specifica le credenziali del canale.
- Registra i tipi di inserimento delle dipendenze con l'oggetto dell'app IServiceCollection.
Scrivere resolver personalizzati e servizi di bilanciamento del carico
Il bilanciamento del carico lato client è estendibile:
- Implementare
Resolver
per creare un sistema di risoluzione personalizzato e risolvere gli indirizzi da una nuova origine dati. - Implementare
LoadBalancer
per creare un servizio di bilanciamento del carico personalizzato con un nuovo comportamento di bilanciamento del carico.
Importante
Le API usate per estendere il bilanciamento del carico lato client sono sperimentali. Possono cambiare senza preavviso.
Creare un sistema di risoluzione personalizzato
Un sistema di risoluzione:
- Implementa
Resolver
e viene creato da un oggettoResolverFactory
. Creare un sistema di risoluzione personalizzato implementando questi tipi. - È responsabile della risoluzione degli indirizzi usati da un servizio di bilanciamento del carico.
- Facoltativamente, è possibile specificare una configurazione del servizio.
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);
}
}
Nel codice precedente:
FileResolverFactory
implementaResolverFactory
. Esegue ilfile
mapping allo schema e creaFileResolver
istanze.FileResolver
implementaPollingResolver
.PollingResolver
è un tipo di base astratto che semplifica l'implementazione di un resolver con logica asincrona eseguendo l'override diResolveAsync
.- In
ResolveAsync
:- L'URI del file viene convertito in un percorso locale. Ad esempio,
file:///c:/addresses.json
diventac:\addresses.json
. - JSON viene caricato dal disco e convertito in una raccolta di indirizzi.
- Il listener viene chiamato con i risultati per informare il canale che gli indirizzi sono disponibili.
- L'URI del file viene convertito in un percorso locale. Ad esempio,
Creare un servizio di bilanciamento del carico personalizzato
Un servizio di bilanciamento del carico:
- Implementa
LoadBalancer
e viene creato da un oggettoLoadBalancerFactory
. Creare un servizio di bilanciamento del carico personalizzato e una factory implementando questi tipi. - Vengono assegnati indirizzi da un sistema di risoluzione e vengono create
Subchannel
istanze. - Tiene traccia dello stato della connessione e crea un oggetto
SubchannelPicker
. Il canale usa internamente la selezione per selezionare gli indirizzi durante l'esecuzione di chiamate gRPC.
è SubchannelsLoadBalancer
:
- Classe di base astratta che implementa
LoadBalancer
. - Gestisce la creazione di
Subchannel
istanze da indirizzi. - Semplifica l'implementazione di un criterio di selezione personalizzato su una raccolta di sottocanali.
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);
}
}
Nel codice precedente:
RandomBalancerFactory
implementaLoadBalancerFactory
. Esegue il mapping al nome delrandom
criterio e creaRandomBalancer
istanze.RandomBalancer
implementaSubchannelsLoadBalancer
. Crea un oggettoRandomPicker
che seleziona in modo casuale un sottocanale.
Configurare resolver personalizzati e servizi di bilanciamento del carico
I resolver personalizzati e i servizi di bilanciamento del carico devono essere registrati con inserimento delle dipendenze (DI) quando vengono usati. Sono disponibili due opzioni:
- Se un'app usa già l'inserimento delle dipendenze, ad esempio un'app Web ASP.NET Core, può essere registrata con la configurazione di inserimento delle dipendenze esistente. Un IServiceProvider oggetto può essere risolto dall'inserimento di dipendenze e passato al canale tramite
GrpcChannelOptions.ServiceProvider
. - Se un'app non usa l'inserimento delle dipendenze, creare:
- Oggetto ServiceCollection con tipi registrati con esso.
- Provider di servizi che usa 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);
Il codice precedente:
- Crea un oggetto
ServiceCollection
e registra nuove implementazioni del sistema di risoluzione e del bilanciamento del carico. - Crea un canale configurato per l'uso delle nuove implementazioni:
ServiceCollection
è integrato in unIServiceProvider
oggetto e impostato suGrpcChannelOptions.ServiceProvider
.- L'indirizzo del canale è
file:///c:/data/addresses.json
. Lo schema esegue ilfile
mapping aFileResolverFactory
. service config
il nome del servizio di bilanciamento del carico èrandom
. Esegue il mapping aRandomLoadBalancerFactory
.
Perché il bilanciamento del carico è importante
HTTP/2 multiplexes multiple calls on a single TCP connection (HTTP/2 multiplexes on a single TCP connection). Se gRPC e HTTP/2 vengono usati con un servizio di bilanciamento del carico di rete (NLB), la connessione viene inoltrata a un server e tutte le chiamate gRPC vengono inviate a tale server. Le altre istanze del server nel bilanciamento carico di rete sono inattive.
I servizi di bilanciamento del carico di rete sono una soluzione comune per il bilanciamento del carico perché sono veloci e leggeri. Ad esempio, Kubernetes usa per impostazione predefinita un servizio di bilanciamento del carico di rete per bilanciare le connessioni tra istanze di pod. Tuttavia, i servizi di bilanciamento del carico di rete non sono efficaci durante la distribuzione del carico quando vengono usati con gRPC e HTTP/2.
Bilanciamento del carico sul lato proxy o sul lato client?
GRPC e HTTP/2 possono essere efficacemente bilanciati tramite un proxy del servizio di bilanciamento del carico dell'applicazione o il bilanciamento del carico lato client. Entrambe queste opzioni consentono la distribuzione di singole chiamate gRPC tra i server disponibili. La scelta tra il bilanciamento del carico sul lato client e il proxy è una scelta architetturale. Ci sono vantaggi e svantaggi per ognuno.
Proxy: le chiamate gRPC vengono inviate al proxy, il proxy prende una decisione di bilanciamento del carico e la chiamata gRPC viene inviata all'endpoint finale. Il proxy è responsabile della conoscenza degli endpoint. L'uso di un proxy aggiunge:
- Un hop di rete aggiuntivo per le chiamate gRPC.
- Latenza e utilizzo di risorse aggiuntive.
- Il server proxy deve essere configurato e configurato correttamente.
Bilanciamento del carico lato client: il client gRPC prende una decisione di bilanciamento del carico all'avvio di una chiamata gRPC. La chiamata gRPC viene inviata direttamente all'endpoint finale. Quando si usa il bilanciamento del carico lato client:
- Il client è responsabile della conoscenza degli endpoint disponibili e delle decisioni di bilanciamento del carico.
- È necessaria una configurazione client aggiuntiva.
- Le chiamate gRPC con bilanciamento del carico ad alte prestazioni eliminano la necessità di un proxy.