【问题标题】:gRPC client-side load balancinggRPC 客户端负载均衡
【发布时间】:2022-01-17 21:54:26
【问题描述】:

我不确定我是否正确理解了通道和客户端负载平衡在 grpc 中的工作方式。我所做的一切都是基于one tutorial

我有几台服务器要处理请求。我写了一个简单的 NameResolverProvider。

public class BalancingNameResolverProvider extends NameResolverProvider {
    private Set<String> replicas;
    private Optional<Map<String, Object>> config;
    private String schema;
    private int priority = 5;

    public BalancingNameResolverProvider(Set<String> replicas, Optional<Map<String, Object>> config, String schema, int priority) {
        this.replicas = replicas;
        this.config = config;
        this.schema = schema;
        this.priority = priority;
    }

    @Override
    protected boolean isAvailable() {
        return true;
    }

    @Override
    protected int priority() {
        return priority;
    }

    @Override
    public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
        List<EquivalentAddressGroup> delegates = replicas.stream()
                .map(x -> new InetSocketAddress(x.split(":")[0], Integer.parseInt(x.split(":")[1])))
                .map(EquivalentAddressGroup::new)
                .collect(Collectors.toList());

        return new NameResolver() {
            private Optional<NameResolver.ConfigOrError> parsedConfig = config.map(x ->
                    args.getServiceConfigParser().parseServiceConfig(x)
            );

            @Override
            public String getServiceAuthority() {
                return targetUri.getAuthority();
            }

            @Override
            public void shutdown() {
            }

            @Override
            public void start(final NameResolver.Listener2 listener) {
                ResolutionResult.Builder builder = ResolutionResult.newBuilder()
                        .setAddresses(delegates)
                        .setAttributes(Attributes.EMPTY);
                parsedConfig.ifPresent(builder::setServiceConfig);
                listener.onResult(builder.build());
            }
        };
    }

    @Override
    public String getDefaultScheme() {
        return schema;
    }
}

我写了一个简单的客户端。

            NameResolverRegistry.getDefaultRegistry().register(resolverConfig.toProvider());
            ManagedChannel channel = NettyChannelBuilder
                    .forTarget("???") //or forAddress("???")
                    .enableRetry()
                    .usePlaintext()
                    .build();
            try {
                HelloServiceGrpc.HelloServiceBlockingStub client = HelloServiceGrpc.newBlockingStub(channel);
                for (int i = 0; i < count; i++) {
                    System.out.println(client.hello(HelloRequest.newBuilder()
                            .setFirstName("first_" + i)
                            .setLastName("lastName_" + i)
                            .build())
                            .getGreeting());
                }
            } finally {
                channel.shutdown();
            }

但在我查看的所有手册中,要么为通道(forAddress)指定了一个主机和端口,要么在“forTarget()”中指定了某个名称。

但是我有好几台服务器,我该如何指定它们?

在什么时候选择服务器?我正确理解 NameResolverProvider 参与其中,我在其中指定了服务器列表

我使用 round_robin 策略。也许我不需要 NameResolverProvider?


编辑

为了使用dns,我添加了DnsNameResolverProvider。

NameResolverRegistry.getDefaultRegistry().register(new DnsNameResolverProvider());

但我还不明白如何指定两个服务器,例如,forAddressforTarget 中的地址为first.example.com:5000second.example.com:5001。会是什么样子?

【问题讨论】:

    标签: java grpc grpc-java


    【解决方案1】:

    解析多个地址的最简单方法是利用 DNS 或 /etc/hosts 文件。默认 DNS 名称解析器将加载所有地址,您可以调用 managedChannel.defaultLoadBalancingPolicy("round_robin") 连接到所有地址,而不是只连接第一个有效的地址。

    名称解析器由传递给forTarget() 的目标字符串的方案选择。因此,如果您的解析器的getScheme() 返回fixed-replicas,您将传递一个类似fixed-replicas:/// 的字符串作为目标字符串。如果目标字符串中没有方案,则使用默认名称解析器。

    forAddress() 是一种方便的转换为host:port 的目标字符串,但具有管理需要百分比编码才能在 URI 中的 IPv6 地址的逻辑。仅在使用默认名称解析器(通常为 dns)时才有用。

    DnsNameResolver 使用优先级 5,您可能不想覆盖它,因此您可能应该为您的提供者使用较低的优先级,例如 4。

    创建InetSocketAddress 时要小心,确保只传递IP 地址。如果您将主机名传递给它,它将在构造函数中进行 DNS 解析。 NameResolver 不应在调用它们的正常线程中执行 I/O 或阻塞操作。 NameResolver 可以将Args.getOffloadExecutor() 用于 I/O 等。如果您在此处使用主机名,那么您最终会将它们解析为单个 IP 地址,并且永远不会重新解析它们,这意味着如果它们发生更改,您需要重新启动二进制文件。

    【讨论】:

    • 你能告诉我更多关于 dns 的信息吗?我是否正确理解必须使用 DNSNameResolver?所有链接都应该以 dns:// 开头?也许有一些文档或文章?
    • 您可以使用自己的解析器。但是,如果您只想连接到多个后端,那么 DNS 是最简单的。您可以将forAddress()dnsname:port 语法与forTarget() 一起使用,grpc 将解释为与dns:///dnsname:port 相同。
    • 我不确定我是否完全理解您的回答。我仍然不明白如何正确指定服务器。我编辑了我的问题,你能看一下吗?
    • @Violetta,DnsNameResolver 已经注册;你不需要注册它。对于 DNS,您需要一个指向两台服务器的 single 名称。但是由于您示例中的端口不同,因此这是行不通的。所以你确实需要一个新的名称解析器。
    • 谢谢你的回答,我了解dnsNameResolver。但这又回到了如何指定多个服务器的问题。在我原来的问题中,给出了 NameResolverProvider。它适合这样的目的吗?在这种情况下应该在 forTarget 中指定什么?我见过指定任何字符串的示例,但我不明白它是如何工作的