共用方式為


gRPC 用戶端負載平衡

注意

這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前的版本,請參閱 本文的 .NET 9 版本。

作者:James Newton-King

用戶端負載平衡是可讓 gRPC 用戶端以最佳方式分散負載到可用伺服器的一項功能。 本文討論如何在 .NET 設定用戶端負載平衡,以建立可調整且高效能的 gRPC 應用程式。

用戶端負載平衡的使用需求:

設定 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.100127.0.0.101
  • 負載平衡器使用 127.0.0.100:80127.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 位址快取

進行負載平衡時,效能很重要。 快取位址可以消除 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:80localhost: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 呼叫。 httphttps 不再是位址的一部分,配置現在會指定解析器,因此使用負載平衡時必須在通道選項上設定 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 會實作 PollingResolverPollingResolver 是抽象基底類型,可藉由覆寫 ResolveAsync 輕鬆實作具有非同步邏輯的解析器。
  • ResolveAsync中:
    • 檔案 URI 會轉換為本機路徑。 例如,file:///c:/addresses.json 會成為 c:\addresses.json
    • JSON 會從磁碟載入並轉換為位址集合。
    • 使用結果呼叫接聽程式,讓通道知道位址可供使用。

建立自訂負載平衡器

此負載平衡器:

  • 實作 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,則建立:
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。

其他資源