gRPC 用戶端負載平衡
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
用戶端負載平衡是可讓 gRPC 用戶端以最佳方式分散負載到可用伺服器的一項功能。 本文討論如何在 .NET 設定用戶端負載平衡,以建立可調整且高效能的 gRPC 應用程式。
用戶端負載平衡的使用需求:
- .NET 5 或更新版本。
Grpc.Net.Client
2.45.0 或更新版本。
設定 gRPC 用戶端負載平衡
建立通道時會設定用戶端負載平衡。 使用負載平衡時應考慮的兩個元件:
- 解析器:用於解析通道位址。 解析器支援從外部來源取得位址。 這種方式也稱為服務探索。
- 負載平衡器:用於建立連線,並挑選 gRPC 呼叫將使用的位址。
Grpc.Net.Client
中包含解析器和負載平衡器的內建實作在。 也可以透過撰寫自訂解析器和負載平衡器來延伸負載平衡。
位址、連線和其他負載平衡狀態會儲存在 GrpcChannel
執行個體。 執行 gRPC 呼叫以讓負載平衡正常運作時,必須重複使用通道。
注意
部分負載平衡組態會使用相依性插入 (DI)。 不使用 DI 的應用程式則可建立 ServiceCollection 執行個體。
如果應用程式已經有 DI 設定 (例如 ASP.NET Core 網站),則型別應該向現有的 DI 執行個體註冊。 GrpcChannelOptions.ServiceProvider
是透過從 DI 取得 IServiceProvider 來進行設定。
設定解析器
解析器是使用建立通道時所用的位址進行設定。 位址的 URI 配置會指定解析器。
配置 | 類型 | 描述 |
---|---|---|
dns |
DnsResolverFactory |
查詢 DNS 位址記錄的主機名稱以解析位址。 |
static |
StaticResolverFactory |
解析應用程式已指定的位址。 如果應用程式已知道呼叫的位址,則建議使用。 |
通道不會直接呼叫與解析器相符的 URI。 相對地,系統會建立相符的解析器並以解析位址。
例如,使用 GrpcChannel.ForAddress("dns:///my-example-host", new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure })
:
- 配置
dns
對應到DnsResolverFactory
。 系統會為通道建立 DNS 解析器的新執行個體。 - 解析器會對
my-example-host
進行 DNS 查詢並取得兩個結果:127.0.0.100
和127.0.0.101
。 - 負載平衡器使用
127.0.0.100:80
和127.0.0.101:80
來建立連線並進行 gRPC 呼叫。
DnsResolverFactory
DnsResolverFactory
會建立解析器,藉此從外部來源取得位址。 DNS 解析通常用來對具有 Kube 無周邊服務的 Pod 執行個體進行負載平衡。
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" });
上述 程式碼:
- 使用位址
dns:///my-example-host
設定已建立的通道。- 配置
dns
對應到DnsResolverFactory
。 my-example-host
為要解析的主機名稱。- 位址中未指定連接埠,因此會將 gRPC 呼叫傳送至連接埠 80。 這是非安全通道的預設連接埠。 可以選擇性地指定主機名稱後面的連接埠。 例如,
dns:///my-example-host:8080
設定要傳送至連接埠 8080 的 gRPC 呼叫。
- 配置
- 未指定負載平衡器。 通道會預設為「先行選取」負載平衡器。
- 啟動 gRPC 呼叫
SayHello
:- DNS 解析器會取得主機名稱
my-example-host
的位址。 - 「先行選取」負載平衡器嘗試連線到其中一個已解析的位址。
- 將呼叫傳送至通道成功連線的第一個位址。
- DNS 解析器會取得主機名稱
DNS 位址快取
進行負載平衡時,效能很重要。 快取位址可以消除 gRPC 呼叫中的位址解析延遲。 進行第一個 gRPC 呼叫時會叫用解析器,而後續呼叫則會使用快取。
若連線中斷,位址會自動重新整理。 在執行階段的位址變更案例中,重新整理非常重要。 例如,在 Kube 中,重新啟動的 Pod 會觸發 DNS 解析器來重新整理並取得 Pod 的新位址。
根據預設,如果連線中斷,DNS 解析器會重新整理。 DNS 解析器也可以選擇性地定期自行重新整理。 若要快速偵測新的 Pod 執行個體,這麼做很實用。
services.AddSingleton<ResolverFactory>(
sp => new DnsResolverFactory(refreshInterval: TimeSpan.FromSeconds(30)));
上述程式碼會建立具有重新整理間隔的 DnsResolverFactory
,並使用相依性插入為其註冊。 如需如何使用自訂組態解析器的詳細資訊,請參閱設定自訂解析器和負載平衡器。
StaticResolverFactory
靜態解析器由 StaticResolverFactory
提供。 此解析器有以下特性:
- 不會呼叫外部來源。 相對地,用戶端應用程式會設定位址。
- 針對應用程式已知道呼叫位址的情況所設計。
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);
上述 程式碼:
- 建立
StaticResolverFactory
。 此處理站知道兩個位址:localhost:80
和localhost:81
。 - 使用相依性插入 (DI) 註冊處理站。
- 使用下列項目設定建立的通道:
- 位址
static:///my-example-host
。 配置static
會對應到靜態解析器。 - 使用 DI 服務提供者設定
GrpcChannelOptions.ServiceProvider
。
- 位址
此範例會為 DI 建立新的 ServiceCollection。 假設應用程式已有 DI 設定,例如 ASP.NET Core 網站。 在此情況下,應使用現有的 DI 執行個體註冊型別。 GrpcChannelOptions.ServiceProvider
是透過從 DI 取得 IServiceProvider 來進行設定。
設定負載平衡器
負載平衡器是使用 ServiceConfig.LoadBalancingConfigs
集合在 service config
中指定。 會有兩個對應到負載平衡器組態名稱的內建負載平衡器:
名稱 | 類型 | 描述 |
---|---|---|
pick_first |
PickFirstLoadBalancerFactory |
嘗試連線位址,直到成功建立連線為止。 一律對第一個成功的連線進行 gRPC 呼叫。 |
round_robin |
RoundRobinLoadBalancerFactory |
嘗試連線至所有位址。 使用循環配置資源邏輯,將 gRPC 呼叫散發到所有成功的連線。 |
service config
是服務組態的縮寫,以 ServiceConfig
型別表示。 通道可使用以下幾種方式,透過已設定的負載平衡器取得 service config
:
- 應用程式可在使用
GrpcChannelOptions.ServiceConfig
建立通道時指定service config
。 - 或者,解析器可以解析通道的
service config
。 此功能可讓外部來源指定其呼叫端應該如何執行負載平衡。 解析器實作可決定解析器是否支援解析service config
。 使用GrpcChannelOptions.DisableResolverServiceConfig
停用此功能。 - 如果未提供
service config
,或service config
未設定負載平衡器,則通道預設為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" });
上述 程式碼:
- 在
service config
中指定RoundRobinLoadBalancerFactory
。 - 啟動 gRPC 呼叫
SayHello
:DnsResolverFactory
會建立解析器以取得主機名稱my-example-host
的位址。- 「循環配置資源」負載平衡器嘗試連線到所有已解析的位址。
- 使用循環配置資源邏輯平均散發 gRPC 呼叫。
設定通道認證
通道必須知道是否使用傳輸安全性來傳送 gRPC 呼叫。 http
和 https
不再是位址的一部分,配置現在會指定解析器,因此使用負載平衡時必須在通道選項上設定 Credentials
。
ChannelCredentials.SecureSsl
:gRPC 呼叫受到 傳輸層安全性 (TLS) 保護。 相當於https
位址。ChannelCredentials.Insecure
:gRPC 呼叫不會使用傳輸安全性。 相當於http
位址。
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" });
搭配 gRPC 用戶端處理站使用負載平衡
可以將 gRPC 用戶端處理站設定為使用負載平衡:
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();
上述 程式碼:
- 使用負載平衡位址設定用戶端。
- 指定通道認證。
- 使用應用程式的 IServiceCollection 註冊 DI 型別。
撰寫自訂解析器和負載平衡器
用戶端負載平衡可以延伸:
- 實作
Resolver
以建立自訂解析器,並從新資料來源解析位址。 - 實作
LoadBalancer
以建立具有新負載平衡行為的自訂負載平衡器。
重要
用於延伸用戶端負載平衡的 API 具實驗性。 它們可以在未通知的情況下變更。
建立自訂解析器
解析器:
- 實作
Resolver
並由ResolverFactory
建立。 藉由實作這些型別來建立自訂解析器。 - 負責解析負載平衡器使用的位址。
- 可以選擇性提供服務組態。
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);
}
}
在上述程式碼中:
FileResolverFactory
會實作ResolverFactory
。 它會對應至file
配置,並建立FileResolver
執行個體。FileResolver
會實作PollingResolver
。PollingResolver
是抽象基底類型,可藉由覆寫ResolveAsync
輕鬆實作具有非同步邏輯的解析器。- 在
ResolveAsync
中:- 檔案 URI 會轉換為本機路徑。 例如,
file:///c:/addresses.json
會成為c:\addresses.json
。 - JSON 會從磁碟載入並轉換為位址集合。
- 使用結果呼叫接聽程式,讓通道知道位址可供使用。
- 檔案 URI 會轉換為本機路徑。 例如,
建立自訂負載平衡器
此負載平衡器:
- 實作
LoadBalancer
並由LoadBalancerFactory
建立。 藉由實作這些型別來建立自訂負載平衡器和處理站。 - 從解析器指定位址並建立
Subchannel
執行個體。 - 追蹤連線的狀態並建立
SubchannelPicker
。 進行 gRPC 呼叫時,通道會在內部使用選擇器來挑選位址。
SubchannelsLoadBalancer
為:
- 實作
LoadBalancer
的抽象基底類別。 - 可管理從位址建立
Subchannel
執行個體的過程。 - 可讓您透過子通道集合輕鬆實作自訂選擇原則。
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);
}
}
在上述程式碼中:
RandomBalancerFactory
會實作LoadBalancerFactory
。 它會對應至random
原則名稱,並建立RandomBalancer
執行個體。RandomBalancer
會實作SubchannelsLoadBalancer
。 它會建立隨機挑選子通道的RandomPicker
。
設定自訂解析器和負載平衡器
使用自訂解析器和負載平衡器時,必須透過相依性插入 (DI) 為它們註冊。 以下為幾個可用選項:
- 如果應用程式已經使用 DI (例如 ASP.NET Core Web 應用程式),則可以使用現有 DI 組態進行註冊。 可以從 DI 解析 IServiceProvider,並使用
GrpcChannelOptions.ServiceProvider
將其傳遞到通道。 - 如果應用程式未使用 DI,則建立:
- 已註冊型別的 ServiceCollection。
- 使用 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);
上述 程式碼:
- 建立
ServiceCollection
並註冊新的解析器和負載平衡器實作。 - 建立設為使用新實作的通道:
ServiceCollection
內建於IServiceProvider
中並設定為GrpcChannelOptions.ServiceProvider
。- 通道位址為
file:///c:/data/addresses.json
。 配置file
會對應到FileResolverFactory
。 service config
負載平衡器名稱為random
。 對應到RandomLoadBalancerFactory
。
負載平衡為何重要
HTTP/2 會在單一 TCP 連線多工處理多個呼叫。 如果 gRPC 和 HTTP/2 與網路負載平衡器 (NLB) 搭配使用,則會將連線轉送至伺服器,並將所有 gRPC 呼叫傳送至該伺服器。 NLB 上的其他伺服器執行個體為閒置狀態。
網路負載平衡器快速又輕量,是負載平衡的常見解決方案。 例如,Kube 預設會使用網路負載平衡器來平衡 Pod 執行個體之間的連線。 不過,若搭配使用 gRPC 和 HTTP/2 ,網路負載平衡器在散發負載時會無效。
該使用 Proxy 或用戶端負載平衡?
gRPC 和 HTTP/2 可以使用應用程式負載平衡器 Proxy 或用戶端負載平衡,有效進行負載平衡。 這兩個選項都可將個別 gRPC 呼叫散發到可用的伺服器。 決定使用 Proxy 或用戶端負載平衡是架構選擇問題。 各有各的優缺點。
Proxy:gRPC 呼叫傳送至 Proxy、由 Proxy 做出負載平衡決策,然後 gRPC 呼叫會傳送至最終端點。 Proxy 負責了解端點。 使用 Proxy 時會新增:
- gRPC 呼叫的一個額外網路躍點。
- 延遲並耗用額外資源。
- Proxy 伺服器必須正確設定及配置組態。
用戶端負載平衡:gRPC 用戶端會在 gRPC 呼叫啟動時做出負載平衡決策。 gRPC 呼叫會直接傳送到最終端點。 使用用戶端負載平衡時:
- 用戶端負責了解可用端點並做出負載平衡決策。
- 需要配置其他用戶端組態。
- 高效能且負載平衡的 gRPC 呼叫不需要 Proxy。